2020-11-06 18:57:50 +00:00
|
|
|
package backtest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/bbgo"
|
2020-11-06 18:57:50 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/exchange/binance"
|
|
|
|
"github.com/c9s/bbgo/pkg/exchange/max"
|
|
|
|
"github.com/c9s/bbgo/pkg/service"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Exchange struct {
|
|
|
|
sourceExchange types.ExchangeName
|
|
|
|
publicExchange types.Exchange
|
|
|
|
srv *service.BacktestService
|
|
|
|
startTime time.Time
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
account *types.Account
|
2020-11-07 08:08:20 +00:00
|
|
|
config *bbgo.Backtest
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
stream *Stream
|
2020-11-07 11:57:36 +00:00
|
|
|
|
|
|
|
closedOrders map[string][]types.Order
|
|
|
|
matchings map[string]*SimplePriceMatching
|
2020-11-07 12:11:07 +00:00
|
|
|
doneC chan struct{}
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
func NewExchange(sourceExchange types.ExchangeName, srv *service.BacktestService, config *bbgo.Backtest) *Exchange {
|
2020-11-06 18:57:50 +00:00
|
|
|
ex, err := newPublicExchange(sourceExchange)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
if config == nil {
|
|
|
|
panic(errors.New("backtest config can not be nil"))
|
|
|
|
}
|
|
|
|
|
|
|
|
startTime, err := config.ParseStartTime()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
balances := config.Account.Balances.BalanceMap()
|
|
|
|
|
|
|
|
account := &types.Account{
|
|
|
|
MakerCommission: config.Account.MakerCommission,
|
|
|
|
TakerCommission: config.Account.TakerCommission,
|
|
|
|
AccountType: "SPOT", // currently not used
|
|
|
|
}
|
|
|
|
account.UpdateBalances(balances)
|
|
|
|
|
2020-11-07 11:57:36 +00:00
|
|
|
e := &Exchange{
|
2020-11-06 18:57:50 +00:00
|
|
|
sourceExchange: sourceExchange,
|
|
|
|
publicExchange: ex,
|
|
|
|
srv: srv,
|
2020-11-06 19:18:05 +00:00
|
|
|
config: config,
|
|
|
|
account: account,
|
2020-11-06 18:57:50 +00:00
|
|
|
startTime: startTime,
|
2020-11-07 11:57:36 +00:00
|
|
|
matchings: make(map[string]*SimplePriceMatching),
|
|
|
|
closedOrders: make(map[string][]types.Order),
|
2020-11-07 12:11:07 +00:00
|
|
|
doneC: make(chan struct{}),
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
2020-11-07 11:57:36 +00:00
|
|
|
|
|
|
|
return e
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
2020-11-07 12:11:07 +00:00
|
|
|
func (e *Exchange) Done() chan struct{} {
|
|
|
|
return e.doneC
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:57:50 +00:00
|
|
|
func (e *Exchange) NewStream() types.Stream {
|
2020-11-06 19:18:05 +00:00
|
|
|
if e.stream != nil {
|
2020-11-07 11:57:36 +00:00
|
|
|
panic("backtest stream can not be allocated twice")
|
2020-11-06 19:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
e.stream = &Stream{exchange: e}
|
2020-11-07 11:57:36 +00:00
|
|
|
|
|
|
|
for _, symbol := range e.config.Symbols {
|
|
|
|
matching := &SimplePriceMatching{
|
|
|
|
Symbol: symbol,
|
|
|
|
CurrentTime: e.startTime,
|
|
|
|
}
|
|
|
|
matching.BindStream(e.stream)
|
|
|
|
e.matchings[symbol] = matching
|
|
|
|
}
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
return e.stream
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) {
|
2020-11-07 11:57:36 +00:00
|
|
|
for _, order := range orders {
|
|
|
|
symbol := order.Symbol
|
|
|
|
matching, ok := e.matchings[symbol]
|
|
|
|
if !ok {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
if createdOrder != nil {
|
|
|
|
createdOrders = append(createdOrders, *createdOrder)
|
|
|
|
|
|
|
|
// market order can be closed immediately.
|
|
|
|
switch createdOrder.Status {
|
|
|
|
case types.OrderStatusFilled, types.OrderStatusCanceled, types.OrderStatusRejected:
|
|
|
|
e.closedOrders[symbol] = append(e.closedOrders[symbol], *createdOrder)
|
|
|
|
}
|
|
|
|
|
|
|
|
e.stream.EmitOrderUpdate(*createdOrder)
|
|
|
|
}
|
|
|
|
|
|
|
|
if trade != nil {
|
|
|
|
e.stream.EmitTradeUpdate(*trade)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return createdOrders, nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
2020-11-07 11:57:36 +00:00
|
|
|
matching, ok := e.matchings[symbol]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("matching engine is not initialized for symbol %s", symbol)
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(matching.bidOrders, matching.askOrders...), nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) {
|
2020-11-07 11:57:36 +00:00
|
|
|
orders, ok := e.closedOrders[symbol]
|
|
|
|
if !ok {
|
|
|
|
return orders, errors.Errorf("matching engine is not initialized for symbol %s", symbol)
|
|
|
|
}
|
|
|
|
|
|
|
|
return orders, nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
2020-11-07 11:57:36 +00:00
|
|
|
for _, order := range orders {
|
|
|
|
matching, ok := e.matchings[order.Symbol]
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("matching engine is not initialized for symbol %s", order.Symbol)
|
|
|
|
}
|
|
|
|
if err := matching.CancelOrder(order); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
2020-11-06 19:18:05 +00:00
|
|
|
return e.account, nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 19:18:05 +00:00
|
|
|
func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) {
|
|
|
|
return e.account.Balances(), nil
|
2020-11-06 18:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) {
|
|
|
|
return e.publicExchange.QueryKLines(ctx, symbol, interval, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error) {
|
|
|
|
// we don't need query trades for backtest
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) Name() types.ExchangeName {
|
|
|
|
return e.publicExchange.Name()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) PlatformFeeCurrency() string {
|
|
|
|
return e.publicExchange.PlatformFeeCurrency()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
|
|
|
return e.publicExchange.QueryMarkets(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryDepositHistory(ctx context.Context, asset string, since, until time.Time) (allDeposits []types.Deposit, err error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []types.Withdraw, err error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPublicExchange(sourceExchange types.ExchangeName) (types.Exchange, error) {
|
|
|
|
switch sourceExchange {
|
|
|
|
case types.ExchangeBinance:
|
|
|
|
return binance.New("", ""), nil
|
|
|
|
case types.ExchangeMax:
|
|
|
|
return max.New("", ""), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.Errorf("exchange %s is not supported", sourceExchange)
|
|
|
|
}
|