mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
add skeleton strategy. fix most of the tests. fix final asset value
This commit is contained in:
parent
9978a3cf90
commit
05521a98b6
21
config/skeleton.yaml
Normal file
21
config/skeleton.yaml
Normal 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
|
|
@ -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:
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")},
|
||||
|
|
|
@ -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)
|
||||
|
|
369
pkg/fixedpoint/convert_backup
Normal file
369
pkg/fixedpoint/convert_backup
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()),
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user