mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
fix fee calculation and add account balance checking
This commit is contained in:
parent
090011da9e
commit
f69c87b3a8
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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++ {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user