add skeleton strategy. fix most of the tests. fix final asset value

This commit is contained in:
zenix 2022-02-09 19:23:35 +09:00
parent 9978a3cf90
commit 05521a98b6
35 changed files with 774 additions and 308 deletions

21
config/skeleton.yaml Normal file
View File

@ -0,0 +1,21 @@
---
sessions:
binance:
exchange: binance
envVarPrefix: binance
exchangeStrategies:
- on: binance
skeleton:
symbol: BNBBUSD
backtest:
startTime: "2022-01-02"
endTime: "2022-01-19"
symbols:
- BNBBUSD
account:
balances:
BNB: 0
BUSD: 10000

View File

@ -39,8 +39,8 @@ func (report AverageCostPnlReport) Print() {
log.Infof("TRADES SINCE: %v", report.StartTime)
log.Infof("NUMBER OF TRADES: %d", report.NumTrades)
log.Infof("AVERAGE COST: %s", types.USD.FormatMoney(report.AverageCost))
log.Infof("TOTAL BUY VOLUME: %s", report.BuyVolume.String())
log.Infof("TOTAL SELL VOLUME: %s", report.SellVolume.String())
log.Infof("TOTAL BUY VOLUME: %v", report.BuyVolume)
log.Infof("TOTAL SELL VOLUME: %v", report.SellVolume)
log.Infof("STOCK: %s", report.Stock.String())
// FIXME:

View File

@ -15,8 +15,8 @@ func newLimitOrder(symbol string, side types.SideType, price, quantity float64)
Symbol: symbol,
Side: side,
Type: types.OrderTypeLimit,
Quantity: quantity,
Price: price,
Quantity: fixedpoint.NewFromFloat(quantity),
Price: fixedpoint.NewFromFloat(price),
TimeInForce: "GTC",
}
}
@ -38,9 +38,9 @@ func TestSimplePriceMatching_LimitOrder(t *testing.T) {
VolumePrecision: 8,
QuoteCurrency: "USDT",
BaseCurrency: "BTC",
MinNotional: 0.001,
MinAmount: 10.0,
MinQuantity: 0.001,
MinNotional: fixedpoint.MustNewFromString("0.001"),
MinAmount: fixedpoint.MustNewFromString("10.0"),
MinQuantity: fixedpoint.MustNewFromString("0.001"),
}
engine := &SimplePriceMatching{

View File

@ -4,40 +4,53 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func TestAdjustQuantityByMinAmount(t *testing.T) {
type args struct {
quantity, price, minAmount float64
quantity, price, minAmount fixedpoint.Value
}
type testcase struct {
name string
args args
wanted float64
wanted string
}
tests := []testcase{
{
name: "amount too small",
args: args{0.1, 10.0, 10.0},
wanted: 1.0,
args: args{
fixedpoint.MustNewFromString("0.1"),
fixedpoint.MustNewFromString("10.0"),
fixedpoint.MustNewFromString("10.0"),
},
wanted: "1.0",
},
{
name: "amount equals to min amount",
args: args{1.0, 10.0, 10.0},
wanted: 1.0,
args: args{
fixedpoint.MustNewFromString("1.0"),
fixedpoint.MustNewFromString("10.0"),
fixedpoint.MustNewFromString("10.0"),
},
wanted: "1.0",
},
{
name: "amount is greater than min amount",
args: args{2.0, 10.0, 10.0},
wanted: 2.0,
args: args{
fixedpoint.MustNewFromString("2.0"),
fixedpoint.MustNewFromString("10.0"),
fixedpoint.MustNewFromString("10.0"),
},
wanted: "2.0",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
q := AdjustFloatQuantityByMinAmount(test.args.quantity, test.args.price, test.args.minAmount)
assert.Equal(t, test.wanted, q)
assert.Equal(t, test.wanted, q.String())
})
}
}

View File

@ -5,9 +5,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
const Delta = 1e-9
func TestExponentialScale(t *testing.T) {
// graph see: https://www.desmos.com/calculator/ip0ijbcbbf
scale := ExponentialScale{
@ -19,8 +20,8 @@ func TestExponentialScale(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.001000 * 1.002305 ^ (x - 1000.000000)", scale.String())
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.001), fixedpoint.NewFromFloat(scale.Call(1000.0))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.01), fixedpoint.NewFromFloat(scale.Call(2000.0))))
assert.InDelta(t, 0.001, scale.Call(1000.0), Delta)
assert.InDelta(t, 0.01, scale.Call(2000.0), Delta)
for x := 1000; x <= 2000; x += 100 {
y := scale.Call(float64(x))
@ -38,8 +39,8 @@ func TestExponentialScale_Reverse(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.100000 * 0.995405 ^ (x - 1000.000000)", scale.String())
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.1), fixedpoint.NewFromFloat(scale.Call(1000.0))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.001), fixedpoint.NewFromFloat(scale.Call(2000.0))))
assert.InDelta(t, 0.1, scale.Call(1000.0), Delta)
assert.InDelta(t, 0.001, scale.Call(2000.0), Delta)
for x := 1000; x <= 2000; x += 100 {
y := scale.Call(float64(x))
@ -57,8 +58,8 @@ func TestLogScale(t *testing.T) {
err := scale.Solve()
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.001303 * log(x - 999.000000) + 0.001000", scale.String())
assert.True(t, fixedpoint.CmpEqDelta(fixedpoint.NewFromFloat(0.001), fixedpoint.NewFromFloat(scale.Call(1000.0)), 1e-9))
assert.True(t, fixedpoint.CmpEqDelta(fixedpoint.NewFromFloat(0.01), fixedpoint.NewFromFloat(scale.Call(2000.0)), 1e-9))
assert.InDelta(t, 0.001, scale.Call(1000.0), Delta)
assert.InDelta(t, 0.01, scale.Call(2000.0), Delta)
for x := 1000; x <= 2000; x += 100 {
y := scale.Call(float64(x))
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
@ -74,8 +75,8 @@ func TestLinearScale(t *testing.T) {
err := scale.Solve()
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.007000 * x + -4.000000", scale.String())
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(3), fixedpoint.NewFromFloat(scale.Call(1000))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(10), fixedpoint.NewFromFloat(scale.Call(2000))))
assert.InDelta(t, 3, scale.Call(1000), Delta)
assert.InDelta(t, 10, scale.Call(2000), Delta)
for x := 1000; x <= 2000; x += 100 {
y := scale.Call(float64(x))
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
@ -91,8 +92,8 @@ func TestLinearScale2(t *testing.T) {
err := scale.Solve()
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.150000 * x + -0.050000", scale.String())
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.1), fixedpoint.NewFromFloat(scale.Call(1))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(0.4), fixedpoint.NewFromFloat(scale.Call(3))))
assert.InDelta(t, 0.1, scale.Call(1), Delta)
assert.InDelta(t, 0.4, scale.Call(3), Delta)
}
func TestQuadraticScale(t *testing.T) {
@ -105,9 +106,9 @@ func TestQuadraticScale(t *testing.T) {
err := scale.Solve()
assert.NoError(t, err)
assert.Equal(t, "f(x) = 0.000550 * x ^ 2 + 0.135000 * x + 1.000000", scale.String())
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(1), fixedpoint.NewFromFloat(scale.Call(0))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(20), fixedpoint.NewFromFloat(scale.Call(100.0))))
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(50.0), fixedpoint.NewFromFloat(scale.Call(200.0))))
assert.InDelta(t, 1, scale.Call(0), Delta)
assert.InDelta(t, 20, scale.Call(100.0), Delta)
assert.InDelta(t, 50.0, scale.Call(200.0), Delta)
for x := 0; x <= 200; x += 1 {
y := scale.Call(float64(x))
t.Logf("%s = %f", scale.FormulaOf(float64(x)), y)
@ -127,11 +128,11 @@ func TestPercentageScale(t *testing.T) {
v, err := s.Scale(0.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(1.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 1.0, v, Delta)
v, err = s.Scale(1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 100.0, v, Delta)
})
t.Run("from -1.0 to 1.0", func(t *testing.T) {
@ -146,11 +147,11 @@ func TestPercentageScale(t *testing.T) {
v, err := s.Scale(-1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(10.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 10.0, v, Delta)
v, err = s.Scale(1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 100.0, v, Delta)
})
t.Run("reverse -1.0 to 1.0", func(t *testing.T) {
@ -165,19 +166,19 @@ func TestPercentageScale(t *testing.T) {
v, err := s.Scale(-1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 100.0, v, Delta)
v, err = s.Scale(1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(10.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 10.0, v, Delta)
v, err = s.Scale(2.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(10.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 10.0, v, Delta)
v, err = s.Scale(-2.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 100.0, v, Delta)
})
t.Run("negative range", func(t *testing.T) {
@ -192,10 +193,11 @@ func TestPercentageScale(t *testing.T) {
v, err := s.Scale(0.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(-100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, -100.0, v, Delta)
v, err = s.Scale(1.0)
assert.NoError(t, err)
assert.True(t, fixedpoint.CmpEq(fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(v)))
assert.InDelta(t, 100.0, v, Delta)
})
}

View File

@ -381,14 +381,14 @@ var BacktestCmd = &cobra.Command{
}
initQuoteAsset := inQuoteAsset(initBalances, market, startPrice)
finalQuoteAsset := inQuoteAsset(finalBalances, market, startPrice)
finalQuoteAsset := inQuoteAsset(finalBalances, market, lastPrice)
log.Infof("INITIAL ASSET IN %s ~= %s %s (1 %s = %v)", market.QuoteCurrency, market.FormatQuantity(initQuoteAsset), market.QuoteCurrency, market.BaseCurrency, startPrice)
log.Infof("FINAL ASSET IN %s ~= %s %s (1 %s = %v)", market.QuoteCurrency, market.FormatQuantity(finalQuoteAsset), market.QuoteCurrency, market.BaseCurrency, lastPrice)
if report.Profit.Sign() > 0 {
color.Green("REALIZED PROFIT: +%v %s", report.Profit, market.QuoteCurrency)
} else {
color.Red("REALIZED PROFIT: %f %s", report.Profit.Float64(), market.QuoteCurrency)
color.Red("REALIZED PROFIT: %v %s", report.Profit, market.QuoteCurrency)
}
if report.UnrealizedProfit.Sign() > 0 {

View File

@ -22,4 +22,5 @@ import (
_ "github.com/c9s/bbgo/pkg/strategy/xmaker"
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
_ "github.com/c9s/bbgo/pkg/strategy/skeleton"
)

View File

@ -159,7 +159,7 @@ var PnLCmd = &cobra.Command{
}
log.Infof("found checkpoints: %+v", checkpoints)
log.Infof("stock: %f", stockManager.Stocks.Quantity())
log.Infof("stock: %v", stockManager.Stocks.Quantity())
tickers, err := exchange.QueryTickers(ctx, symbol)

View File

@ -10,14 +10,16 @@ import (
"github.com/stretchr/testify/assert"
)
var itov = fixedpoint.NewFromInt
func TestDepthBuffer_ReadyState(t *testing.T) {
buf := NewBuffer(func() (book types.SliceOrderBook, finalID int64, err error) {
return types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: 1},
{Price: itov(100), Volume: itov(1)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: 1},
{Price: itov(99), Volume: itov(1)},
},
}, 33, nil
})
@ -33,10 +35,10 @@ func TestDepthBuffer_ReadyState(t *testing.T) {
buf.AddUpdate(
types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: fixedpoint.Value(updateID)},
{Price: itov(100), Volume: itov(updateID)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: fixedpoint.Value(updateID)},
{Price: itov(99), Volume: itov(updateID)},
},
}, updateID)
}
@ -52,10 +54,10 @@ func TestDepthBuffer_CorruptedUpdateAtTheBeginning(t *testing.T) {
snapshotFinalID += 30
return types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: 1},
{Price: itov(100), Volume: itov(1)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: 1},
{Price: itov(99), Volume: itov(1)},
},
}, snapshotFinalID, nil
})
@ -74,10 +76,10 @@ func TestDepthBuffer_CorruptedUpdateAtTheBeginning(t *testing.T) {
buf.AddUpdate(types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: fixedpoint.Value(updateID)},
{Price: itov(100), Volume: itov(updateID)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: fixedpoint.Value(updateID)},
{Price: itov(99), Volume: itov(updateID)},
},
}, updateID)
}
@ -92,10 +94,10 @@ func TestDepthBuffer_ConcurrentRun(t *testing.T) {
time.Sleep(10 * time.Millisecond)
return types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: 1},
{Price: itov(100), Volume: itov(1)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: 1},
{Price: itov(99), Volume: itov(1)},
},
}, snapshotFinalID, nil
})
@ -138,10 +140,10 @@ func TestDepthBuffer_ConcurrentRun(t *testing.T) {
buf.AddUpdate(types.SliceOrderBook{
Bids: types.PriceVolumeSlice{
{Price: 100, Volume: fixedpoint.Value(updateID)},
{Price: itov(100), Volume: itov(updateID)},
},
Asks: types.PriceVolumeSlice{
{Price: 99, Volume: fixedpoint.Value(updateID)},
{Price: itov(99), Volume: itov(updateID)},
},
}, updateID)

View File

@ -40,14 +40,14 @@ func Test_toGlobalOrderFromOpenOrder(t *testing.T) {
assert.Equal(t, "XRP-PERP", o.Symbol)
assert.Equal(t, types.SideTypeSell, o.Side)
assert.Equal(t, types.OrderTypeLimit, o.Type)
assert.Equal(t, float64(31431), o.Quantity)
assert.Equal(t, 0.306525, o.Price)
assert.Equal(t, "31431", o.Quantity.String())
assert.Equal(t, "0.306525", o.Price.String())
assert.Equal(t, "GTC", o.TimeInForce)
assert.Equal(t, types.ExchangeFTX, o.Exchange)
assert.True(t, o.IsWorking)
assert.Equal(t, uint64(9596912), o.OrderID)
assert.Equal(t, types.OrderStatusPartiallyFilled, o.Status)
assert.Equal(t, float64(10), o.ExecutedQuantity)
assert.Equal(t, "10", o.ExecutedQuantity.String())
}
func TestTrimLowerString(t *testing.T) {

View File

@ -421,9 +421,9 @@ func TestExchange_QueryMarkets(t *testing.T) {
VolumePrecision: 4,
QuoteCurrency: "USD",
BaseCurrency: "BTC",
MinQuantity: 0.001,
StepSize: 0.0001,
TickSize: 1,
MinQuantity: fixedpoint.NewFromFloat(0.001),
StepSize: fixedpoint.NewFromFloat(0.0001),
TickSize: fixedpoint.NewFromInt(1),
}, resp["BTCUSD"])
}
@ -468,7 +468,7 @@ func TestExchange_QueryDepositHistory(t *testing.T) {
assert.Equal(t, types.Deposit{
Exchange: types.ExchangeFTX,
Time: types.Time(actualConfirmedTime),
Amount: 99.0,
Amount: fixedpoint.NewFromInt(99),
Asset: "TUSD",
TransactionID: "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1",
Status: types.DepositSuccess,
@ -603,15 +603,15 @@ func TestExchange_QueryTrades(t *testing.T) {
ID: 789,
OrderID: 456,
Exchange: types.ExchangeFTX,
Price: 672.5,
Quantity: 1.0,
QuoteQuantity: 672.5 * 1.0,
Price: fixedpoint.NewFromFloat(672.5),
Quantity: fixedpoint.One,
QuoteQuantity: fixedpoint.NewFromFloat(672.5 * 1.0),
Symbol: "TSLAUSD",
Side: types.SideTypeSell,
IsBuyer: false,
IsMaker: true,
Time: types.Time(actualConfirmedTime),
Fee: -0.0033625,
Fee: fixedpoint.NewFromFloat(-0.0033625),
FeeCurrency: "USD",
IsMargin: false,
IsIsolated: false,

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func Test_messageHandler_handleMessage(t *testing.T) {
@ -46,14 +47,14 @@ func Test_messageHandler_handleMessage(t *testing.T) {
Symbol: "OXY-PERP",
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Quantity: 1.0,
Price: 2.7185,
Quantity: fixedpoint.One,
Price: fixedpoint.NewFromFloat(2.7185),
TimeInForce: "GTC",
},
Exchange: types.ExchangeFTX,
OrderID: 36379,
Status: types.OrderStatusFilled,
ExecutedQuantity: 1.0,
ExecutedQuantity: fixedpoint.One,
CreationTime: types.Time(mustParseDatetime("2021-03-28T06:12:50.991447+00:00")),
UpdateTime: types.Time(mustParseDatetime("2021-03-28T06:12:50.991447+00:00")),
}, order)
@ -96,15 +97,15 @@ func Test_messageHandler_handleMessage(t *testing.T) {
ID: 6276431,
OrderID: 323789,
Exchange: types.ExchangeFTX,
Price: 2.723,
Quantity: 1.0,
QuoteQuantity: 2.723 * 1.0,
Price: fixedpoint.NewFromFloat(2.723),
Quantity: fixedpoint.One,
QuoteQuantity: fixedpoint.NewFromFloat(2.723 * 1.0),
Symbol: "OXY-PERP",
Side: types.SideTypeBuy,
IsBuyer: true,
IsMaker: false,
Time: types.Time(mustParseDatetime("2021-03-28T06:12:34.702926+00:00")),
Fee: 0.00153917575,
Fee: fixedpoint.NewFromFloat(0.00153917575),
FeeCurrency: "USD",
IsMargin: false,
IsIsolated: false,

View File

@ -231,14 +231,14 @@ func Test_websocketResponse_toOrderUpdateResponse(t *testing.T) {
Market: "SOL/USD",
Type: "limit",
Side: "buy",
Price: 0.5,
Size: 100,
Price: fixedpoint.NewFromFloat(0.5),
Size: fixedpoint.NewFromInt(100),
Status: "closed",
FilledSize: 0.0,
RemainingSize: 0.0,
FilledSize: fixedpoint.Zero,
RemainingSize: fixedpoint.Zero,
ReduceOnly: false,
Liquidation: false,
AvgFillPrice: 0,
AvgFillPrice: fixedpoint.Zero,
PostOnly: false,
Ioc: false,
CreatedAt: datetime{Time: mustParseDatetime("2021-03-27T11:00:36.418674+00:00")},

View File

@ -261,7 +261,7 @@ queryRecentlyClosedOrders:
log.Debugf("skipping duplicated order: %d", order.OrderID)
}
log.Debugf("max order %d %s %f %s %s", order.OrderID, order.Symbol, order.Price, order.Status, order.CreationTime.Time().Format(time.StampMilli))
log.Debugf("max order %d %s %v %s %s", order.OrderID, order.Symbol, order.Price, order.Status, order.CreationTime.Time().Format(time.StampMilli))
orderIDs[order.OrderID] = struct{}{}
orders = append(orders, *order)

View File

@ -0,0 +1,369 @@
package fixedpoint
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"strconv"
"sync/atomic"
)
const MaxPrecision = 12
const DefaultPrecision = 8
const DefaultPow = 1e8
type Value int64
func (v Value) Value() (driver.Value, error) {
return v.Float64(), nil
}
func (v *Value) Scan(src interface{}) error {
switch d := src.(type) {
case int64:
*v = Value(d)
return nil
case float64:
*v = NewFromFloat(d)
return nil
case []byte:
vv, err := NewFromString(string(d))
if err != nil {
return err
}
*v = vv
return nil
default:
}
return fmt.Errorf("fixedpoint.Value scan error, type: %T is not supported, value; %+v", src, src)
}
func (v Value) Float64() float64 {
return float64(v) / DefaultPow
}
func (v Value) Abs() Value {
if v < 0 {
return -v
}
return v
}
func (v Value) String() string {
return strconv.FormatFloat(float64(v)/DefaultPow, 'f', -1, 64)
}
func (v Value) Percentage() string {
return fmt.Sprintf("%.2f%%", v.Float64()*100.0)
}
func (v Value) SignedPercentage() string {
if v > 0 {
return "+" + v.Percentage()
}
return v.Percentage()
}
func (v Value) Int64() int64 {
return int64(v.Float64())
}
func (v Value) Int() int {
return int(v.Float64())
}
// BigMul is the math/big version multiplication
func (v Value) BigMul(v2 Value) Value {
x := new(big.Int).Mul(big.NewInt(int64(v)), big.NewInt(int64(v2)))
return Value(x.Int64() / DefaultPow)
}
func (v Value) Mul(v2 Value) Value {
return NewFromFloat(v.Float64() * v2.Float64())
}
func (v Value) MulInt(v2 int) Value {
return NewFromFloat(v.Float64() * float64(v2))
}
func (v Value) MulFloat64(v2 float64) Value {
return NewFromFloat(v.Float64() * v2)
}
func (v Value) Div(v2 Value) Value {
return NewFromFloat(v.Float64() / v2.Float64())
}
func (v Value) DivFloat64(v2 float64) Value {
return NewFromFloat(v.Float64() / v2)
}
func (v Value) Floor() Value {
return NewFromFloat(math.Floor(v.Float64()))
}
func (v Value) Ceil() Value {
return NewFromFloat(math.Ceil(v.Float64()))
}
func (v Value) Sub(v2 Value) Value {
return Value(int64(v) - int64(v2))
}
func (v Value) Add(v2 Value) Value {
return Value(int64(v) + int64(v2))
}
func (v *Value) AtomicAdd(v2 Value) {
atomic.AddInt64((*int64)(v), int64(v2))
}
func (v *Value) AtomicLoad() Value {
i := atomic.LoadInt64((*int64)(v))
return Value(i)
}
func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) {
var f float64
if err = unmarshal(&f); err == nil {
*v = NewFromFloat(f)
return
}
var i int64
if err = unmarshal(&i); err == nil {
*v = NewFromInt64(i)
return
}
var s string
if err = unmarshal(&s); err == nil {
nv, err2 := NewFromString(s)
if err2 == nil {
*v = nv
return
}
}
return err
}
func (v Value) MarshalJSON() ([]byte, error) {
f := float64(v) / DefaultPow
o := strconv.FormatFloat(f, 'f', 8, 64)
return []byte(o), nil
}
func (v *Value) UnmarshalJSON(data []byte) error {
var a interface{}
var err = json.Unmarshal(data, &a)
if err != nil {
return err
}
switch d := a.(type) {
case float64:
*v = NewFromFloat(d)
case float32:
*v = NewFromFloat32(d)
case int:
*v = NewFromInt(d)
case int64:
*v = NewFromInt64(d)
case string:
v2, err := NewFromString(d)
if err != nil {
return err
}
*v = v2
default:
return fmt.Errorf("unsupported type: %T %v", d, d)
}
return nil
}
func Must(v Value, err error) Value {
if err != nil {
panic(err)
}
return v
}
var ErrPrecisionLoss = errors.New("precision loss")
func Parse(input string) (num int64, numDecimalPoints int, err error) {
length := len(input)
isPercentage := input[length-1] == '%'
if isPercentage {
length -= 1
input = input[0:length]
}
var neg int64 = 1
var digit int64
for i := 0; i < length; i++ {
c := input[i]
if c == '-' {
neg = -1
} else if c >= '0' && c <= '9' {
digit, err = strconv.ParseInt(string(c), 10, 64)
if err != nil {
return
}
num = num*10 + digit
} else if c == '.' {
i++
if i > len(input)-1 {
err = fmt.Errorf("expect fraction numbers after dot")
return
}
for j := i; j < len(input); j++ {
fc := input[j]
if fc >= '0' && fc <= '9' {
digit, err = strconv.ParseInt(string(fc), 10, 64)
if err != nil {
return
}
numDecimalPoints++
num = num*10 + digit
if numDecimalPoints >= MaxPrecision {
return num, numDecimalPoints, ErrPrecisionLoss
}
} else {
err = fmt.Errorf("expect digit, got %c", fc)
return
}
}
break
} else {
err = fmt.Errorf("unexpected char %c", c)
return
}
}
num = num * neg
if isPercentage {
numDecimalPoints += 2
}
return num, numDecimalPoints, nil
}
func NewFromAny(any interface{}) (Value, error) {
switch v := any.(type) {
case string:
return NewFromString(v)
case float64:
return NewFromFloat(v), nil
case int64:
return NewFromInt64(v), nil
default:
return 0, fmt.Errorf("fixedpoint unsupported type %v", v)
}
}
func NewFromString(input string) (Value, error) {
length := len(input)
if length == 0 {
return 0, nil
}
isPercentage := input[length-1] == '%'
if isPercentage {
input = input[0 : length-1]
}
v, err := strconv.ParseFloat(input, 64)
if err != nil {
return 0, err
}
if isPercentage {
v = v * 0.01
}
return NewFromFloat(v), nil
}
func MustNewFromString(input string) Value {
v, err := NewFromString(input)
if err != nil {
panic(fmt.Errorf("can not parse %s into fixedpoint, error: %s", input, err.Error()))
}
return v
}
func NewFromFloat(val float64) Value {
return Value(int64(math.Round(val * DefaultPow)))
}
func NewFromFloat32(val float32) Value {
return Value(int64(math.Round(float64(val) * DefaultPow)))
}
func NewFromInt(val int) Value {
return Value(int64(val * DefaultPow))
}
func NewFromInt64(val int64) Value {
return Value(val * DefaultPow)
}
func NumFractionalDigits(a Value) int {
numPow := 0
for pow := int64(DefaultPow); pow%10 != 1; pow /= 10 {
numPow++
}
numZeros := 0
for v := int64(a); v%10 == 0; v /= 10 {
numZeros++
}
return numPow - numZeros
}
func Min(a, b Value) Value {
if a < b {
return a
}
return b
}
func Max(a, b Value) Value {
if a > b {
return a
}
return b
}
func Abs(a Value) Value {
if a < 0 {
return -a
}
return a
}

View File

@ -1000,6 +1000,11 @@ func (v Value) MarshalJSON() ([]byte, error) {
}
func (v *Value) UnmarshalJSON(data []byte) error {
// FIXME: do we need to compare {}, [], "", or "null"?
if bytes.Compare(data, []byte{'n', 'u', 'l', 'l'}) == 0 {
*v = Zero
return nil
}
var err error
if *v, err = NewFromBytes(data); err != nil {
return err

View File

@ -84,6 +84,8 @@ func TestRound(t *testing.T) {
func TestFromString(t *testing.T) {
f := MustNewFromString("0.004075")
assert.Equal(t, "0.004075", f.String())
f = MustNewFromString("0.03")
assert.Equal(t, "0.03", f.String())
}
// Not used

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func TestDepositService(t *testing.T) {
@ -24,7 +25,7 @@ func TestDepositService(t *testing.T) {
err = service.Insert(types.Deposit{
Exchange: types.ExchangeMax,
Time: types.Time(time.Now()),
Amount: 0.001,
Amount: fixedpoint.NewFromFloat(0.001),
Asset: "BTC",
Address: "test",
TransactionID: "02",

View File

@ -30,7 +30,7 @@ func TestRewardService_InsertAndQueryUnspent(t *testing.T) {
Exchange: "max",
Type: "commission",
Currency: "BTC",
Quantity: 1,
Quantity: fixedpoint.One,
State: "done",
Spent: false,
CreatedAt: types.Time(time.Now()),
@ -48,7 +48,7 @@ func TestRewardService_InsertAndQueryUnspent(t *testing.T) {
Exchange: "max",
Type: "airdrop",
Currency: "MAX",
Quantity: 1000000,
Quantity: fixedpoint.NewFromInt(1000000),
State: "done",
Spent: false,
CreatedAt: types.Time(time.Now()),
@ -95,7 +95,7 @@ func TestRewardService_AggregateUnspentCurrencyPosition(t *testing.T) {
Exchange: "max",
Type: "commission",
Currency: "BTC",
Quantity: 1,
Quantity: fixedpoint.One,
State: "done",
Spent: false,
CreatedAt: types.Time(now),
@ -107,7 +107,7 @@ func TestRewardService_AggregateUnspentCurrencyPosition(t *testing.T) {
Exchange: "max",
Type: "commission",
Currency: "LTC",
Quantity: 2,
Quantity: fixedpoint.NewFromInt(2),
State: "done",
Spent: false,
CreatedAt: types.Time(now),
@ -119,7 +119,7 @@ func TestRewardService_AggregateUnspentCurrencyPosition(t *testing.T) {
Exchange: "max",
Type: "airdrop",
Currency: "MAX",
Quantity: 1000000,
Quantity: fixedpoint.NewFromInt(1000000),
State: "done",
Spent: false,
CreatedAt: types.Time(now),
@ -133,9 +133,9 @@ func TestRewardService_AggregateUnspentCurrencyPosition(t *testing.T) {
v, ok := currencyPositions["LTC"]
assert.True(t, ok)
assert.Equal(t, fixedpoint.Value(2), v)
assert.Equal(t, fixedpoint.NewFromInt(2), v)
v, ok = currencyPositions["BTC"]
assert.True(t, ok)
assert.Equal(t, fixedpoint.Value(1), v)
assert.Equal(t, fixedpoint.One, v)
}

View File

@ -118,7 +118,7 @@ func (s *TradeService) Sync(ctx context.Context, exchange types.Exchange, symbol
tradeKeys[key] = struct{}{}
log.Infof("inserting trade: %s %d %s %-4s price: %-13f volume: %-11f %5s %s",
log.Infof("inserting trade: %s %d %s %-4s price: %-13v volume: %-11v %5s %s",
trade.Exchange,
trade.ID,
trade.Symbol,

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func Test_tradeService(t *testing.T) {
@ -27,9 +28,9 @@ func Test_tradeService(t *testing.T) {
ID: 1,
OrderID: 1,
Exchange: "binance",
Price: 1000.0,
Quantity: 0.1,
QuoteQuantity: 1000.0 * 0.1,
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.1),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.1),
Symbol: "BTCUSDT",
Side: "BUY",
IsBuyer: true,

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func TestWithdrawService(t *testing.T) {
@ -24,10 +25,10 @@ func TestWithdrawService(t *testing.T) {
err = service.Insert(types.Withdraw{
Exchange: types.ExchangeMax,
Asset: "BTC",
Amount: 0.0001,
Amount: fixedpoint.NewFromFloat(0.0001),
Address: "test",
TransactionID: "01",
TransactionFee: 0.0001,
TransactionFee: fixedpoint.NewFromFloat(0.0001),
Network: "omni",
ApplyTime: types.Time(time.Now()),
})

View File

@ -3,13 +3,15 @@ package skeleton
import (
"context"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
const ID = "skeleton"
var log = logrus.WithField("strategy", ID)
func init() {
bbgo.RegisterStrategy(ID, &Strategy{})
@ -17,8 +19,6 @@ func init() {
type Strategy struct {
Symbol string `json:"symbol"`
types.Market
}
func (s *Strategy) ID() string {
@ -27,28 +27,53 @@ func (s *Strategy) ID() string {
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
log.Infof("subscribe %s", s.Symbol)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
}
var Ten = fixedpoint.NewFromInt(10)
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
session.UserDataStream.OnKLineClosed(func(kline types.KLine) {
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
market, ok := session.Market(s.Symbol)
if !ok {
log.Warnf("fetch market fail %s", s.Symbol)
return nil
}
callback := func(kline types.KLine) {
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
if !ok {
return
}
_ = quoteBalance
quantityAmount := quoteBalance.Available
if quantityAmount.Sign() <= 0 || quantityAmount.Compare(Ten) < 0 {
return
}
currentPrice, ok := session.LastPrice(s.Symbol)
if !ok {
return
}
totalQuantity := quantityAmount.Div(currentPrice)
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: kline.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: 0.01,
Price: currentPrice,
Quantity: totalQuantity,
})
if err != nil {
log.WithError(err).Error("submit order error")
}
}
session.UserDataStream.OnStart(func() {
log.Infof("connected")
})
session.MarketDataStream.OnKLineClosed(callback)
return nil
}

View File

@ -2,7 +2,6 @@ package types
import (
"fmt"
"math"
"sort"
"strings"
"sync"
@ -34,7 +33,7 @@ func (b Balance) Total() fixedpoint.Value {
func (b Balance) String() string {
if b.Locked.Sign() > 0 {
return fmt.Sprintf("%s: %s (locked %s)", b.Currency, b.Available.String(), b.Locked.String())
return fmt.Sprintf("%s: %v (locked %v)", b.Currency, b.Available, b.Locked)
}
return fmt.Sprintf("%s: %s", b.Currency, b.Available.String())
@ -53,23 +52,23 @@ type Asset struct {
type AssetMap map[string]Asset
func (m AssetMap) PlainText() (o string) {
sumUsd := 0.0
sumBTC := 0.0
sumUsd := fixedpoint.Zero
sumBTC := fixedpoint.Zero
for _, a := range m {
usd := a.InUSD.Float64()
btc := a.InBTC.Float64()
usd := a.InUSD
btc := a.InBTC
o += fmt.Sprintf(" %s: %s (≈ %s) (≈ %s)",
a.Currency,
a.Total.String(),
USD.FormatMoneyFloat64(usd),
BTC.FormatMoneyFloat64(btc),
USD.FormatMoney(usd),
BTC.FormatMoney(btc),
) + "\n"
sumUsd += usd
sumBTC += btc
sumUsd = sumUsd.Add(usd)
sumBTC = sumBTC.Add(btc)
}
o += fmt.Sprintf(" Summary: (≈ %s) (≈ %s)",
USD.FormatMoneyFloat64(sumUsd),
BTC.FormatMoneyFloat64(sumBTC),
USD.FormatMoney(sumUsd),
BTC.FormatMoney(sumBTC),
) + "\n"
return o
}
@ -100,11 +99,11 @@ func (m AssetMap) SlackAttachment() slack.Attachment {
for _, a := range assets {
fields = append(fields, slack.AttachmentField{
Title: a.Currency,
Value: fmt.Sprintf("%s (≈ %s) (≈ %s) (%.2f%%)",
Value: fmt.Sprintf("%s (≈ %s) (≈ %s) (%s)",
a.Total.String(),
USD.FormatMoneyFloat64(a.InUSD.Float64()),
BTC.FormatMoneyFloat64(a.InBTC.Float64()),
math.Round(a.InUSD.Div(totalUSD).Float64()*100.0),
USD.FormatMoney(a.InUSD),
BTC.FormatMoney(a.InBTC),
a.InUSD.Div(totalUSD).FormatPercentage(2),
),
Short: false,
})
@ -112,8 +111,8 @@ func (m AssetMap) SlackAttachment() slack.Attachment {
return slack.Attachment{
Title: fmt.Sprintf("Net Asset Value %s (≈ %s)",
USD.FormatMoneyFloat64(totalUSD.Float64()),
BTC.FormatMoneyFloat64(totalBTC.Float64()),
USD.FormatMoney(totalUSD),
BTC.FormatMoney(totalBTC),
),
Fields: fields,
}
@ -192,9 +191,9 @@ func (m BalanceMap) Print() {
}
if balance.Locked.Sign() > 0 {
logrus.Infof(" %s: %s (locked %s)", balance.Currency, balance.Available.String(), balance.Locked.String())
logrus.Infof(" %s: %v (locked %v)", balance.Currency, balance.Available, balance.Locked)
} else {
logrus.Infof(" %s: %s", balance.Currency, balance.Available.String())
logrus.Infof(" %s: %v", balance.Currency, balance.Available)
}
}
}
@ -314,7 +313,7 @@ func (a *Account) UseLockedBalance(currency string, fund fixedpoint.Value) error
return nil
}
return fmt.Errorf("trying to use more than locked: locked %s < want to use %s", balance.Locked.String(), fund.String())
return fmt.Errorf("trying to use more than locked: locked %v < want to use %v", balance.Locked, fund)
}
func (a *Account) UnlockBalance(currency string, unlocked fixedpoint.Value) error {
@ -326,8 +325,8 @@ func (a *Account) UnlockBalance(currency string, unlocked fixedpoint.Value) erro
return fmt.Errorf("trying to unlocked inexisted balance: %s", currency)
}
if unlocked.Compare(balance.Locked) > 0 {
return fmt.Errorf("trying to unlocked more than locked %s: locked %s < want to unlock %s", currency, balance.Locked.String(), unlocked.String())
if unlocked.Compare(balance.Locked) >= 0 {
return fmt.Errorf("trying to unlocked more than locked %s: locked %v < want to unlock %v", currency, balance.Locked, unlocked)
}
balance.Locked = balance.Locked.Sub(unlocked)
@ -343,12 +342,12 @@ func (a *Account) LockBalance(currency string, locked fixedpoint.Value) error {
balance, ok := a.balances[currency]
if ok && balance.Available.Compare(locked) >= 0 {
balance.Locked = balance.Locked.Add(locked)
balance.Available = balance.Locked.Sub(locked)
balance.Available = balance.Available.Sub(locked)
a.balances[currency] = balance
return nil
}
return fmt.Errorf("insufficient available balance %s for lock: want to lock %s, available %s", currency, locked.String(), balance.Available.String())
return fmt.Errorf("insufficient available balance %s for lock: want to lock %v, available %v", currency, locked, balance.Available)
}
func (a *Account) UpdateBalances(balances BalanceMap) {
@ -385,10 +384,10 @@ func (a *Account) Print() {
}
if a.MakerFeeRate.Sign() > 0 {
logrus.Infof("maker fee rate: %s", a.MakerFeeRate.String())
logrus.Infof("maker fee rate: %v", a.MakerFeeRate)
}
if a.TakerFeeRate.Sign() > 0 {
logrus.Infof("taker fee rate: %s", a.TakerFeeRate.String())
logrus.Infof("taker fee rate: %v", a.TakerFeeRate)
}
a.balances.Print()

View File

@ -10,53 +10,53 @@ import (
func TestAccountLockAndUnlock(t *testing.T) {
a := NewAccount()
a.AddBalance("USDT", 1000)
a.AddBalance("USDT", fixedpoint.NewFromInt(1000))
var err error
balance, ok := a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(1000))
assert.Equal(t, balance.Locked, fixedpoint.Value(0))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(1000))
assert.Equal(t, balance.Locked, fixedpoint.Zero)
err = a.LockBalance("USDT", fixedpoint.Value(100))
err = a.LockBalance("USDT", fixedpoint.NewFromInt(100))
assert.NoError(t, err)
balance, ok = a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(900))
assert.Equal(t, balance.Locked, fixedpoint.Value(100))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(900))
assert.Equal(t, balance.Locked, fixedpoint.NewFromInt(100))
err = a.UnlockBalance("USDT", 100)
err = a.UnlockBalance("USDT", fixedpoint.NewFromInt(100))
assert.NoError(t, err)
balance, ok = a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(1000))
assert.Equal(t, balance.Locked, fixedpoint.Value(0))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(1000))
assert.Equal(t, balance.Locked, fixedpoint.Zero)
}
func TestAccountLockAndUse(t *testing.T) {
a := NewAccount()
a.AddBalance("USDT", 1000)
a.AddBalance("USDT", fixedpoint.NewFromInt(1000))
var err error
balance, ok := a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(1000))
assert.Equal(t, balance.Locked, fixedpoint.Value(0))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(1000))
assert.Equal(t, balance.Locked, fixedpoint.Zero)
err = a.LockBalance("USDT", 100)
err = a.LockBalance("USDT", fixedpoint.NewFromInt(100))
assert.NoError(t, err)
balance, ok = a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(900))
assert.Equal(t, balance.Locked, fixedpoint.Value(100))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(900))
assert.Equal(t, balance.Locked, fixedpoint.NewFromInt(100))
err = a.UseLockedBalance("USDT", 100)
err = a.UseLockedBalance("USDT", fixedpoint.NewFromInt(100))
assert.NoError(t, err)
balance, ok = a.Balance("USDT")
assert.True(t, ok)
assert.Equal(t, balance.Available, fixedpoint.Value(900))
assert.Equal(t, balance.Locked, fixedpoint.Value(0))
assert.Equal(t, balance.Available, fixedpoint.NewFromInt(900))
assert.Equal(t, balance.Locked, fixedpoint.Zero)
}

View File

@ -6,21 +6,30 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
var s func(string)fixedpoint.Value = fixedpoint.MustNewFromString
func TestFormatQuantity(t *testing.T) {
quantity := formatQuantity(0.12511, 0.01)
quantity := formatQuantity(
s("0.12511"),
s("0.01"))
assert.Equal(t, "0.12", quantity)
quantity = formatQuantity(0.12511, 0.001)
quantity = formatQuantity(
s("0.12511"),
s("0.001"))
assert.Equal(t, "0.125", quantity)
}
func TestFormatPrice(t *testing.T) {
price := formatPrice(26.288256, 0.0001)
price := formatPrice(
s("26.288256"),
s("0.0001"))
assert.Equal(t, "26.2882", price)
price = formatPrice(26.288656, 0.001)
price = formatPrice(s("26.288656"), s("0.001"))
assert.Equal(t, "26.288", price)
}

View File

@ -11,8 +11,8 @@ import (
func prepareOrderBookBenchmarkData() (asks, bids PriceVolumeSlice) {
for p := 0.0; p < 1000.0; p++ {
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.NewFromFloat(1)})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.NewFromFloat(1)})
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.One})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.One})
}
return
}
@ -20,8 +20,8 @@ func prepareOrderBookBenchmarkData() (asks, bids PriceVolumeSlice) {
func BenchmarkOrderBook_Load(b *testing.B) {
var asks, bids = prepareOrderBookBenchmarkData()
for p := 0.0; p < 1000.0; p++ {
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.NewFromFloat(1)})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.NewFromFloat(1)})
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.One})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.One})
}
b.Run("RBTOrderBook", func(b *testing.B) {
@ -52,8 +52,8 @@ func BenchmarkOrderBook_Load(b *testing.B) {
func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
var asks, bids = prepareOrderBookBenchmarkData()
for p := 0.0; p < 1000.0; p += 2 {
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.NewFromFloat(1)})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.NewFromFloat(1)})
asks = append(asks, PriceVolume{fixedpoint.NewFromFloat(1000 + p), fixedpoint.One})
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.One})
}
rbBook := NewRBOrderBook("ETHUSDT")
@ -67,10 +67,10 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
b.Run("RBTOrderBook", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
if price >= fixedpoint.NewFromFloat(1000) {
rbBook.Asks.Upsert(price, fixedpoint.NewFromFloat(1))
if price.Compare(fixedpoint.NewFromInt(1000)) >= 0 {
rbBook.Asks.Upsert(price, fixedpoint.One)
} else {
rbBook.Bids.Upsert(price, fixedpoint.NewFromFloat(1))
rbBook.Bids.Upsert(price, fixedpoint.One)
}
}
})
@ -87,7 +87,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
b.Run("OrderBook", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
if price >= fixedpoint.NewFromFloat(1000) {
if price.Compare(fixedpoint.NewFromFloat(1000)) >= 0 {
sliceBook.Asks = sliceBook.Asks.Upsert(PriceVolume{Price: price, Volume: fixedpoint.NewFromFloat(1)}, false)
} else {
sliceBook.Bids = sliceBook.Bids.Upsert(PriceVolume{Price: price, Volume: fixedpoint.NewFromFloat(1)}, true)
@ -134,5 +134,5 @@ func TestOrderBook_IsValid(t *testing.T) {
}
isValid, err = ob.IsValid()
assert.False(t, isValid)
assert.EqualError(t, err, "bid price 80000.000000 > ask price 100.000000")
assert.EqualError(t, err, "bid price 80000 > ask price 100")
}

View File

@ -216,17 +216,17 @@ func (p *Position) SlackAttachment() slack.Attachment {
func (p *Position) PlainText() (msg string) {
posType := p.Type()
msg = fmt.Sprintf("%s Position %s: average cost = %s, base = %s, quote = %s",
msg = fmt.Sprintf("%s Position %s: average cost = %v, base = %v, quote = %v",
posType,
p.Symbol,
p.AverageCost.String(),
p.Base.String(),
p.Quote.String(),
p.AverageCost,
p.Base,
p.Quote,
)
if p.TotalFee != nil {
for feeCurrency, fee := range p.TotalFee {
msg += fmt.Sprintf("\nfee (%s) = %s", feeCurrency, fee.String())
msg += fmt.Sprintf("\nfee (%s) = %v", feeCurrency, fee)
}
}
@ -234,11 +234,11 @@ func (p *Position) PlainText() (msg string) {
}
func (p *Position) String() string {
return fmt.Sprintf("POSITION %s: average cost = %f, base = %f, quote = %f",
return fmt.Sprintf("POSITION %s: average cost = %v, base = %v, quote = %v",
p.Symbol,
p.AverageCost.String(),
p.Base.String(),
p.Quote.String(),
p.AverageCost,
p.Base,
p.Quote,
)
}
@ -364,6 +364,7 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
Add(quoteQuantity).
Sub(feeInQuote).
Div(dividor)
p.AverageCost = p.AverageCost.Mul(p.Base.Neg()).
Add(quoteQuantity).
Div(dividor)

View File

@ -17,42 +17,44 @@ func TestPosition_ExchangeFeeRate_Short(t *testing.T) {
QuoteCurrency: "USDT",
}
feeRate := 0.075 * 0.01
feeRate := fixedpoint.NewFromFloat(0.075 * 0.01)
pos.SetExchangeFeeRate(ExchangeBinance, ExchangeFee{
MakerFeeRate: fixedpoint.NewFromFloat(feeRate),
TakerFeeRate: fixedpoint.NewFromFloat(feeRate),
MakerFeeRate: feeRate,
TakerFeeRate: feeRate,
})
quantity := 10.0
quoteQuantity := 3000.0 * quantity
fee := quoteQuantity * feeRate
averageCost := (quoteQuantity - fee) / quantity
bnbPrice := 570.0
quantity := fixedpoint.NewFromInt(10)
quoteQuantity := fixedpoint.NewFromInt(3000).Mul(quantity)
fee := quoteQuantity.Mul(feeRate)
averageCost := quoteQuantity.Sub(fee).Div(quantity)
bnbPrice := fixedpoint.NewFromInt(570)
pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 3000.0,
Price: fixedpoint.NewFromInt(3000),
Quantity: quantity,
QuoteQuantity: quoteQuantity,
Symbol: "BTCUSDT",
Side: SideTypeSell,
Fee: fee / bnbPrice,
Fee: fee.Div(bnbPrice),
FeeCurrency: "BNB",
})
_, netProfit, madeProfit := pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 2000.0,
Quantity: 10.0,
QuoteQuantity: 2000.0 * 10.0,
Price: fixedpoint.NewFromInt(2000),
Quantity: fixedpoint.NewFromInt(10),
QuoteQuantity: fixedpoint.NewFromInt(2000 * 10),
Symbol: "BTCUSDT",
Side: SideTypeBuy,
Fee: 2000.0 * 10.0 * feeRate / bnbPrice,
Fee: fixedpoint.NewFromInt(2000 * 10.0).Mul(feeRate).Div(bnbPrice),
FeeCurrency: "BNB",
})
expectedProfit := (averageCost-2000.0)*10.0 - (2000.0 * 10.0 * feeRate)
expectedProfit := averageCost.Sub(fixedpoint.NewFromInt(2000)).
Mul(fixedpoint.NewFromInt(10)).
Sub(fixedpoint.NewFromInt(2000).Mul(fixedpoint.NewFromInt(10)).Mul(feeRate))
assert.True(t, madeProfit)
assert.InDelta(t, expectedProfit, netProfit.Float64(), Delta)
assert.Equal(t, expectedProfit, netProfit)
}
func TestPosition_ExchangeFeeRate_Long(t *testing.T) {
@ -62,108 +64,115 @@ func TestPosition_ExchangeFeeRate_Long(t *testing.T) {
QuoteCurrency: "USDT",
}
feeRate := 0.075 * 0.01
feeRate := fixedpoint.NewFromFloat(0.075 * 0.01)
pos.SetExchangeFeeRate(ExchangeBinance, ExchangeFee{
MakerFeeRate: fixedpoint.NewFromFloat(feeRate),
TakerFeeRate: fixedpoint.NewFromFloat(feeRate),
MakerFeeRate: feeRate,
TakerFeeRate: feeRate,
})
quantity := 10.0
quoteQuantity := 3000.0 * quantity
fee := quoteQuantity * feeRate
averageCost := (quoteQuantity + fee) / quantity
bnbPrice := 570.0
quantity := fixedpoint.NewFromInt(10)
quoteQuantity := fixedpoint.NewFromInt(3000).Mul(quantity)
fee := quoteQuantity.Mul(feeRate)
averageCost := quoteQuantity.Add(fee).Div(quantity)
bnbPrice := fixedpoint.NewFromInt(570)
pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 3000.0,
Price: fixedpoint.NewFromInt(3000),
Quantity: quantity,
QuoteQuantity: quoteQuantity,
Symbol: "BTCUSDT",
Side: SideTypeBuy,
Fee: fee / bnbPrice,
Fee: fee.Div(bnbPrice),
FeeCurrency: "BNB",
})
_, netProfit, madeProfit := pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 4000.0,
Quantity: 10.0,
QuoteQuantity: 4000.0 * 10.0,
Price: fixedpoint.NewFromInt(4000),
Quantity: fixedpoint.NewFromInt(10),
QuoteQuantity: fixedpoint.NewFromInt(4000).Mul(fixedpoint.NewFromInt(10)),
Symbol: "BTCUSDT",
Side: SideTypeSell,
Fee: 4000.0 * 10.0 * feeRate / bnbPrice,
Fee: fixedpoint.NewFromInt(40000).Mul(feeRate).Div(bnbPrice),
FeeCurrency: "BNB",
})
expectedProfit := (4000.0-averageCost)*10.0 - (4000.0 * 10.0 * feeRate)
expectedProfit := fixedpoint.NewFromInt(4000).
Sub(averageCost).Mul(fixedpoint.NewFromInt(10)).
Sub(fixedpoint.NewFromInt(40000).Mul(feeRate))
assert.True(t, madeProfit)
assert.InDelta(t, expectedProfit, netProfit.Float64(), Delta)
assert.Equal(t, expectedProfit, netProfit)
}
func TestPosition(t *testing.T) {
var feeRate float64 = 0.05 * 0.01
feeRateValue := fixedpoint.NewFromFloat(feeRate)
var testcases = []struct {
name string
trades []Trade
expectedAverageCost float64
expectedBase float64
expectedQuote float64
expectedProfit float64
expectedAverageCost fixedpoint.Value
expectedBase fixedpoint.Value
expectedQuote fixedpoint.Value
expectedProfit fixedpoint.Value
}{
{
name: "base fee",
trades: []Trade{
{
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
Fee: 0.01 * 0.05 * 0.01, // 0.05%
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.01),
Fee: fixedpoint.MustNewFromString("0.000005"), // 0.01 * 0.05 * 0.01
FeeCurrency: "BTC",
},
},
expectedAverageCost: (1000.0 * 0.01) / (0.01 * (1.0 - feeRate)),
expectedBase: 0.01 - (0.01 * feeRate),
expectedQuote: 0 - 1000.0*0.01,
expectedProfit: 0.0,
expectedAverageCost: fixedpoint.NewFromFloat(1000.0 * 0.01).
Div(fixedpoint.NewFromFloat(0.01).Mul(fixedpoint.One.Sub(feeRateValue))),
expectedBase: fixedpoint.NewFromFloat(0.01).
Sub(fixedpoint.NewFromFloat(0.01).Mul(feeRateValue)),
expectedQuote: fixedpoint.NewFromFloat(0 - 1000.0*0.01),
expectedProfit: fixedpoint.Zero,
},
{
name: "quote fee",
trades: []Trade{
{
Side: SideTypeSell,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
Fee: (1000.0 * 0.01) * feeRate, // 0.05%
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.01),
Fee: fixedpoint.NewFromFloat((1000.0 * 0.01) * feeRate), // 0.05%
FeeCurrency: "USDT",
},
},
expectedAverageCost: (1000.0 * 0.01 * (1.0 - feeRate)) / 0.01,
expectedBase: -0.01,
expectedQuote: 0.0 + 1000.0 * 0.01 * (1.0 - feeRate),
expectedProfit: 0.0,
expectedAverageCost: fixedpoint.NewFromFloat(1000.0 * 0.01).
Mul(fixedpoint.One.Sub(feeRateValue)).
Div(fixedpoint.NewFromFloat(0.01)),
expectedBase: fixedpoint.NewFromFloat(-0.01),
expectedQuote: fixedpoint.NewFromFloat(0.0 + 1000.0 * 0.01 * (1.0 - feeRate)),
expectedProfit: fixedpoint.Zero,
},
{
name: "long",
trades: []Trade{
{
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.01),
},
{
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
Price: fixedpoint.NewFromInt(2000),
Quantity: fixedpoint.MustNewFromString("0.03"),
QuoteQuantity: fixedpoint.NewFromFloat(2000.0 * 0.03),
},
},
expectedAverageCost: (1000.0*0.01 + 2000.0*0.03) / 0.04,
expectedBase: 0.01 + 0.03,
expectedQuote: 0 - 1000.0*0.01 - 2000.0*0.03,
expectedProfit: 0.0,
expectedAverageCost: fixedpoint.NewFromFloat((1000.0*0.01 + 2000.0*0.03) / 0.04),
expectedBase: fixedpoint.NewFromFloat(0.01 + 0.03),
expectedQuote: fixedpoint.NewFromFloat(0 - 1000.0*0.01 - 2000.0*0.03),
expectedProfit: fixedpoint.Zero,
},
{
@ -171,27 +180,27 @@ func TestPosition(t *testing.T) {
trades: []Trade{
{
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.01),
},
{
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
Price: fixedpoint.NewFromInt(2000),
Quantity: fixedpoint.MustNewFromString("0.03"),
QuoteQuantity: fixedpoint.NewFromFloat(2000.0 * 0.03),
},
{
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.01,
QuoteQuantity: 3000.0 * 0.01,
Price: fixedpoint.NewFromInt(3000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(3000.0 * 0.01),
},
},
expectedAverageCost: (1000.0*0.01 + 2000.0*0.03) / 0.04,
expectedBase: 0.03,
expectedQuote: 0 - 1000.0*0.01 - 2000.0*0.03 + 3000.0*0.01,
expectedProfit: (3000.0 - (1000.0*0.01+2000.0*0.03)/0.04) * 0.01,
expectedAverageCost: fixedpoint.NewFromFloat((1000.0*0.01 + 2000.0*0.03) / 0.04),
expectedBase: fixedpoint.MustNewFromString("0.03"),
expectedQuote: fixedpoint.NewFromFloat(0 - 1000.0*0.01 - 2000.0*0.03 + 3000.0*0.01),
expectedProfit: fixedpoint.NewFromFloat((3000.0 - (1000.0*0.01+2000.0*0.03)/0.04) * 0.01),
},
{
@ -199,28 +208,28 @@ func TestPosition(t *testing.T) {
trades: []Trade{
{
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
Price: fixedpoint.NewFromInt(1000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(1000.0 * 0.01),
},
{
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
Price: fixedpoint.NewFromInt(2000),
Quantity: fixedpoint.MustNewFromString("0.03"),
QuoteQuantity: fixedpoint.NewFromFloat(2000.0 * 0.03),
},
{
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.10,
QuoteQuantity: 3000.0 * 0.10,
Price: fixedpoint.NewFromInt(3000),
Quantity: fixedpoint.NewFromFloat(0.10),
QuoteQuantity: fixedpoint.NewFromFloat(3000.0 * 0.10),
},
},
expectedAverageCost: 3000.0,
expectedBase: -0.06,
expectedQuote: -1000.0*0.01 - 2000.0*0.03 + 3000.0*0.1,
expectedProfit: (3000.0 - (1000.0*0.01+2000.0*0.03)/0.04) * 0.04,
expectedAverageCost: fixedpoint.NewFromInt(3000),
expectedBase: fixedpoint.MustNewFromString("-0.06"),
expectedQuote: fixedpoint.NewFromFloat(-1000.0*0.01 - 2000.0*0.03 + 3000.0*0.1),
expectedProfit: fixedpoint.NewFromFloat((3000.0 - (1000.0*0.01+2000.0*0.03)/0.04) * 0.04),
},
{
@ -228,22 +237,22 @@ func TestPosition(t *testing.T) {
trades: []Trade{
{
Side: SideTypeSell,
Price: 2000.0,
Quantity: 0.01,
QuoteQuantity: 2000.0 * 0.01,
Price: fixedpoint.NewFromInt(2000),
Quantity: fixedpoint.NewFromFloat(0.01),
QuoteQuantity: fixedpoint.NewFromFloat(2000.0 * 0.01),
},
{
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.03,
QuoteQuantity: 3000.0 * 0.03,
Price: fixedpoint.NewFromInt(3000),
Quantity: fixedpoint.MustNewFromString("0.03"),
QuoteQuantity: fixedpoint.NewFromFloat(3000.0 * 0.03),
},
},
expectedAverageCost: (2000.0*0.01 + 3000.0*0.03) / (0.01 + 0.03),
expectedBase: 0 - 0.01 - 0.03,
expectedQuote: 2000.0*0.01 + 3000.0*0.03,
expectedProfit: 0.0,
expectedAverageCost: fixedpoint.NewFromFloat((2000.0*0.01 + 3000.0*0.03) / (0.01 + 0.03)),
expectedBase: fixedpoint.NewFromFloat(0 - 0.01 - 0.03),
expectedQuote: fixedpoint.NewFromFloat(2000.0*0.01 + 3000.0*0.03),
expectedProfit: fixedpoint.Zero,
},
}
@ -255,11 +264,11 @@ func TestPosition(t *testing.T) {
QuoteCurrency: "USDT",
}
profitAmount, _, profit := pos.AddTrades(testcase.trades)
assert.InDelta(t, testcase.expectedQuote, pos.Quote.Float64(), Delta, "expectedQuote")
assert.InDelta(t, testcase.expectedBase, pos.Base.Float64(), Delta, "expectedBase")
assert.InDelta(t, testcase.expectedAverageCost, pos.AverageCost.Float64(), Delta, "expectedAverageCost")
assert.Equal(t, testcase.expectedQuote, pos.Quote, "expectedQuote")
assert.Equal(t, testcase.expectedBase, pos.Base, "expectedBase")
assert.Equal(t, testcase.expectedAverageCost, pos.AverageCost, "expectedAverageCost")
if profit {
assert.InDelta(t, testcase.expectedProfit, profitAmount.Float64(), Delta, "expectedProfit")
assert.Equal(t, testcase.expectedProfit, profitAmount, "expectedProfit")
}
})
}

View File

@ -4,26 +4,27 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func TestPriceVolumeSlice_Remove(t *testing.T) {
for _, descending := range []bool{true, false} {
slice := PriceVolumeSlice{}
slice = slice.Upsert(PriceVolume{Price: 1}, descending)
slice = slice.Upsert(PriceVolume{Price: 3}, descending)
slice = slice.Upsert(PriceVolume{Price: 5}, descending)
slice = slice.Upsert(PriceVolume{Price: fixedpoint.One}, descending)
slice = slice.Upsert(PriceVolume{Price: fixedpoint.NewFromInt(3)}, descending)
slice = slice.Upsert(PriceVolume{Price: fixedpoint.NewFromInt(5)}, descending)
assert.Equal(t, 3, len(slice), "with descending %v", descending)
slice = slice.Remove(2, descending)
slice = slice.Remove(fixedpoint.NewFromInt(2), descending)
assert.Equal(t, 3, len(slice), "with descending %v", descending)
slice = slice.Remove(3, descending)
slice = slice.Remove(fixedpoint.NewFromInt(3), descending)
assert.Equal(t, 2, len(slice), "with descending %v", descending)
slice = slice.Remove(99, descending)
slice = slice.Remove(fixedpoint.NewFromInt(99), descending)
assert.Equal(t, 2, len(slice), "with descending %v", descending)
slice = slice.Remove(0, descending)
slice = slice.Remove(fixedpoint.Zero, descending)
assert.Equal(t, 2, len(slice), "with descending %v", descending)
}
}

View File

@ -11,11 +11,11 @@ func TestRBOrderBook_EmptyBook(t *testing.T) {
book := NewRBOrderBook("BTCUSDT")
bid, ok := book.BestBid()
assert.False(t, ok)
assert.Equal(t, fixedpoint.Value(0), bid.Price)
assert.Equal(t, fixedpoint.Zero, bid.Price)
ask, ok := book.BestAsk()
assert.False(t, ok)
assert.Equal(t, fixedpoint.Value(0), ask.Price)
assert.Equal(t, fixedpoint.Zero, ask.Price)
}
func TestRBOrderBook_Load(t *testing.T) {
@ -24,10 +24,10 @@ func TestRBOrderBook_Load(t *testing.T) {
book.Load(SliceOrderBook{
Symbol: "BTCUSDT",
Bids: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2800.0), Volume: fixedpoint.NewFromFloat(1.0)},
{Price: fixedpoint.NewFromFloat(2800.0), Volume: fixedpoint.One},
},
Asks: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2810.0), Volume: fixedpoint.NewFromFloat(1.0)},
{Price: fixedpoint.NewFromFloat(2810.0), Volume: fixedpoint.One},
},
})
@ -46,10 +46,10 @@ func TestRBOrderBook_LoadAndDelete(t *testing.T) {
book.Load(SliceOrderBook{
Symbol: "BTCUSDT",
Bids: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2800.0), Volume: fixedpoint.NewFromFloat(1.0)},
{Price: fixedpoint.NewFromFloat(2800.0), Volume: fixedpoint.One},
},
Asks: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2810.0), Volume: fixedpoint.NewFromFloat(1.0)},
{Price: fixedpoint.NewFromFloat(2810.0), Volume: fixedpoint.One},
},
})
@ -64,10 +64,10 @@ func TestRBOrderBook_LoadAndDelete(t *testing.T) {
book.Load(SliceOrderBook{
Symbol: "BTCUSDT",
Bids: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2800.0), Volume: 0},
{Price: fixedpoint.NewFromFloat(2800.0), Volume: fixedpoint.Zero},
},
Asks: PriceVolumeSlice{
{Price: fixedpoint.NewFromFloat(2810.0), Volume: 0},
{Price: fixedpoint.NewFromFloat(2810.0), Volume: fixedpoint.Zero},
},
})

View File

@ -468,7 +468,7 @@ func (tree *RBTree) CopyInorder(limit int) *RBTree {
func (tree *RBTree) Print() {
tree.Inorder(func(n *RBNode) bool {
fmt.Printf("%f -> %f\n", n.key.Float64(), n.value.Float64())
fmt.Printf("%v -> %v\n", n.key, n.value)
return true
})
}

View File

@ -9,20 +9,22 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
)
var itov func(int64)fixedpoint.Value = fixedpoint.NewFromInt
func TestRBTree_InsertAndDelete(t *testing.T) {
tree := NewRBTree()
node := tree.Rightmost()
assert.Nil(t, node)
tree.Insert(fixedpoint.NewFromInt(10), 10)
tree.Insert(fixedpoint.NewFromInt(9), 9)
tree.Insert(fixedpoint.NewFromInt(12), 12)
tree.Insert(fixedpoint.NewFromInt(11), 11)
tree.Insert(fixedpoint.NewFromInt(13), 13)
tree.Insert(itov(10), itov(10))
tree.Insert(itov(9), itov(9))
tree.Insert(itov(12), itov(12))
tree.Insert(itov(11), itov(11))
tree.Insert(itov(13), itov(13))
node = tree.Rightmost()
assert.Equal(t, fixedpoint.NewFromInt(13), node.key)
assert.Equal(t, fixedpoint.Value(13), node.value)
assert.Equal(t, itov(13), node.key)
assert.Equal(t, itov(13), node.value)
ok := tree.Delete(fixedpoint.NewFromInt(12))
assert.True(t, ok, "should delete the node successfully")
@ -33,15 +35,15 @@ func TestRBTree_Rightmost(t *testing.T) {
node := tree.Rightmost()
assert.Nil(t, node, "should be nil")
tree.Insert(10, 10)
tree.Insert(itov(10), itov(10))
node = tree.Rightmost()
assert.Equal(t, fixedpoint.Value(10), node.key)
assert.Equal(t, fixedpoint.Value(10), node.value)
assert.Equal(t, itov(10), node.key)
assert.Equal(t, itov(10), node.value)
tree.Insert(12, 12)
tree.Insert(9, 9)
tree.Insert(itov(12), itov(12))
tree.Insert(itov(9), itov(9))
node = tree.Rightmost()
assert.Equal(t, fixedpoint.Value(12), node.key)
assert.Equal(t, itov(12), node.key)
}
func TestRBTree_RandomInsertSearchAndDelete(t *testing.T) {
@ -153,12 +155,12 @@ func TestRBTree_bulkInsert(t *testing.T) {
}
tree.Inorder(func(n *RBNode) bool {
if n.left != neel {
if !assert.Greater(t, n.key, n.left.key) {
if !assert.True(t, n.key.Compare(n.left.key) > 0) {
return false
}
}
if n.right != neel {
if !assert.Less(t, n.key, n.right.key) {
if !assert.True(t, n.key.Compare(n.right.key) < 0) {
return false
}
}
@ -173,7 +175,7 @@ func TestRBTree_bulkInsertAndDelete(t *testing.T) {
for p := range pvs {
return p
}
return 0
return fixedpoint.Zero
}
var tree = NewRBTree()
@ -185,7 +187,7 @@ func TestRBTree_bulkInsertAndDelete(t *testing.T) {
if i%3 == 0 || i%7 == 0 {
removePrice := getRandomPrice()
if removePrice > 0 {
if removePrice.Sign() > 0 {
if !assert.True(t, tree.Delete(removePrice), "existing price %f should be removed at round %d", removePrice.Float64(), i) {
return
}
@ -205,12 +207,12 @@ func TestRBTree_bulkInsertAndDelete(t *testing.T) {
// validate tree structure
tree.Inorder(func(n *RBNode) bool {
if n.left != neel {
if !assert.Greater(t, n.key, n.left.key) {
if !assert.True(t, n.key.Compare(n.left.key) > 0) {
return false
}
}
if n.right != neel {
if !assert.Less(t, n.key, n.right.key) {
if !assert.True(t, n.key.Compare(n.right.key) < 0) {
return false
}
}

View File

@ -1,6 +1,7 @@
package types
import "testing"
import "github.com/c9s/bbgo/pkg/fixedpoint"
func Test_trimTrailingZero(t *testing.T) {
type args struct {
@ -42,7 +43,7 @@ func Test_trimTrailingZero(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := trimTrailingZero(tt.args.a); got != tt.want {
if got := fixedpoint.MustNewFromString(tt.args.a).String(); got != tt.want {
t.Errorf("trimTrailingZero() = %v, want %v", got, tt.want)
}
})

View File

@ -24,7 +24,7 @@ type Withdraw struct {
}
func (w Withdraw) String() string {
return fmt.Sprintf("withdraw %s %f to %s at %s", w.Asset, w.Amount, w.Address, w.ApplyTime.Time())
return fmt.Sprintf("withdraw %s %v to %s at %s", w.Asset, w.Amount, w.Address, w.ApplyTime.Time())
}
func (w Withdraw) EffectiveTime() time.Time {