fix fee calculation and add account balance checking

This commit is contained in:
c9s 2020-11-08 21:52:44 +08:00
parent 090011da9e
commit f69c87b3a8
6 changed files with 108 additions and 20 deletions

View File

@ -27,6 +27,7 @@ type Exchange struct {
trades map[string][]types.Trade
closedOrders map[string][]types.Order
matchingBooks map[string]*SimplePriceMatching
markets types.MarketMap
doneC chan struct{}
}
@ -40,6 +41,11 @@ func NewExchange(sourceName types.ExchangeName, srv *service.BacktestService, co
panic(errors.New("backtest config can not be nil"))
}
markets, err := bbgo.LoadExchangeMarketsWithCache(context.Background(), ex)
if err != nil {
panic(err)
}
startTime, err := config.ParseStartTime()
if err != nil {
panic(err)
@ -57,6 +63,7 @@ func NewExchange(sourceName types.ExchangeName, srv *service.BacktestService, co
e := &Exchange{
sourceName: sourceName,
publicExchange: ex,
markets: markets,
srv: srv,
config: config,
account: account,
@ -86,9 +93,17 @@ func (e *Exchange) NewStream() types.Stream {
})
for _, symbol := range e.config.Symbols {
market, ok := e.markets[symbol]
if !ok {
panic(errors.Errorf("market %s is undefined", symbol))
}
e.matchingBooks[symbol] = &SimplePriceMatching{
CurrentTime: e.startTime,
Account: e.config.Account,
CurrentTime: e.startTime,
Account: e.account,
Market: market,
MakerCommission: e.config.Account.MakerCommission,
TakerCommission: e.config.Account.TakerCommission,
}
}
@ -103,6 +118,7 @@ func (e Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder)
return nil, errors.Errorf("matching engine is not initialized for symbol %s", symbol)
}
createdOrder, trade, err := matching.PlaceOrder(order)
if err != nil {
return nil, err

View File

@ -7,8 +7,6 @@ import (
"github.com/pkg/errors"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
@ -32,6 +30,7 @@ func incTradeID() uint64 {
// SimplePriceMatching implements a simple kline data driven matching engine for backtest
type SimplePriceMatching struct {
Symbol string
Market types.Market
mu sync.Mutex
bidOrders []types.Order
@ -40,7 +39,10 @@ type SimplePriceMatching struct {
LastPrice fixedpoint.Value
CurrentTime time.Time
Account bbgo.BacktestAccount
Account *types.Account
MakerCommission int `json:"makerCommission"`
TakerCommission int `json:"takerCommission"`
}
func (m *SimplePriceMatching) CancelOrder(o types.Order) (types.Order, error) {
@ -88,16 +90,43 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (closedOrders *typ
// start from one
orderID := incOrderID()
// price for checking account balance
price := o.Price
switch o.Type {
case types.OrderTypeMarket:
price = m.LastPrice.Float64()
case types.OrderTypeLimit:
price = o.Price
}
switch o.Side {
case types.SideTypeBuy:
quote := price * o.Quantity
quoteBalance, ok := m.Account.Balance(m.Market.QuoteCurrency)
if !ok || quote > quoteBalance.Available {
return nil, nil, errors.Errorf("insufficient available quote balance")
}
case types.SideTypeSell:
baseQuantity := o.Quantity
baseBalance, ok := m.Account.Balance(m.Market.BaseCurrency)
if !ok || baseQuantity > baseBalance.Available {
return nil, nil, errors.Errorf("insufficient available base balance")
}
}
if o.Type == types.OrderTypeMarket {
order := newOrder(o, orderID, m.CurrentTime)
order.Status = types.OrderStatusFilled
order.ExecutedQuantity = order.Quantity
order.Price = m.LastPrice.Float64()
order.Price = price
trade := m.newTradeFromOrder(order, false)
return &order, &trade, nil
}
order := newOrder(o, orderID, m.CurrentTime)
switch o.Side {
@ -125,6 +154,21 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool)
commission = 0.0001 * float64(m.Account.TakerCommission) // binance uses 10~15
}
var fee float64
var feeCurrency string
switch order.Side {
case types.SideTypeBuy:
fee = order.Quantity * commission
feeCurrency = m.Market.BaseCurrency
case types.SideTypeSell:
fee = order.Quantity * order.Price * commission
feeCurrency = m.Market.QuoteCurrency
}
var id = incTradeID()
return types.Trade{
ID: int64(id),
@ -138,8 +182,8 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool)
IsBuyer: order.Side == types.SideTypeBuy,
IsMaker: isMaker,
Time: m.CurrentTime,
Fee: order.Quantity * order.Price * commission,
FeeCurrency: "USDT",
Fee: fee,
FeeCurrency: feeCurrency,
}
}

View File

@ -22,8 +22,30 @@ func newLimitOrder(symbol string, side types.SideType, price, quantity float64)
}
func TestSimplePriceMatching(t *testing.T) {
account := types.NewAccount()
account.MakerCommission = 15
account.TakerCommission = 15
account.UpdateBalances(types.BalanceMap{
"USDT": {Currency: "USDT", Available: 1000000.0},
"BTC": {Currency: "BTC", Available: 100.0},
})
market := types.Market{
Symbol: "BTCUSDT",
PricePrecision: 8,
VolumePrecision: 8,
QuoteCurrency: "USDT",
BaseCurrency: "BTC",
MinNotional: 0.001,
MinAmount: 10.0,
MinLot: 0.001,
MinQuantity: 0.001,
}
engine := &SimplePriceMatching{
CurrentTime: time.Now(),
Account: account,
Market: market,
}
for i := 0; i < 5; i++ {

View File

@ -54,7 +54,7 @@ func (s *Stream) Connect(ctx context.Context) error {
if k.Interval == types.Interval1m {
matching, ok := s.exchange.matchingBooks[k.Symbol]
if !ok {
log.Error("matching book of %s is not initialized", k.Symbol)
log.Errorf("matching book of %s is not initialized", k.Symbol)
}
matching.processKLine(s, k)
}

View File

@ -77,14 +77,7 @@ func (environ *Environment) AddExchange(name string, exchange types.Exchange) (s
func (environ *Environment) Init(ctx context.Context) (err error) {
for n := range environ.sessions {
var session = environ.sessions[n]
var markets types.MarketMap
err = WithCache(fmt.Sprintf("%s-markets", session.Exchange.Name()), &markets, func() (interface{}, error) {
return session.Exchange.QueryMarkets(ctx)
})
if err != nil {
return err
}
var markets, err = LoadExchangeMarketsWithCache(ctx, session.Exchange)
if len(markets) == 0 {
return errors.Errorf("market config should not be empty")
@ -174,7 +167,7 @@ func (environ *Environment) Init(ctx context.Context) (err error) {
}
// update last prices by the given kline
lastKLine := kLines[len(kLines) - 1]
lastKLine := kLines[len(kLines)-1]
if lastPriceTime == emptyTime {
session.lastPrices[symbol] = lastKLine.Close
lastPriceTime = lastKLine.EndTime
@ -376,3 +369,10 @@ func (environ *Environment) Connect(ctx context.Context) error {
return nil
}
func LoadExchangeMarketsWithCache(ctx context.Context, ex types.Exchange) (markets types.MarketMap, err error) {
err = WithCache(fmt.Sprintf("%s-markets", ex.Name()), &markets, func() (interface{}, error) {
return ex.QueryMarkets(ctx)
})
return markets, err
}

View File

@ -19,13 +19,19 @@ type BalanceMap map[string]Balance
type Account struct {
sync.Mutex
MakerCommission int `json:"makerCommission"`
TakerCommission int `json:"takerCommission"`
MakerCommission int `json:"makerCommission"`
TakerCommission int `json:"takerCommission"`
AccountType string `json:"accountType"`
balances BalanceMap
}
func NewAccount() *Account {
return &Account{
balances: make(BalanceMap),
}
}
// Balances lock the balances and returned the copied balances
func (a *Account) Balances() BalanceMap {
d := make(BalanceMap)