2020-08-04 01:47:54 +00:00
|
|
|
package bbgo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"github.com/c9s/bbgo/pkg/bbgo/types"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"math"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Stock types.Trade
|
|
|
|
|
2020-08-04 05:50:27 +00:00
|
|
|
func (stock *Stock) String() string {
|
|
|
|
return fmt.Sprintf("%f (%f)", stock.Price, stock.Quantity)
|
|
|
|
}
|
|
|
|
|
2020-08-04 01:47:54 +00:00
|
|
|
func (stock *Stock) Consume(quantity float64) float64 {
|
|
|
|
delta := math.Min(stock.Quantity, quantity)
|
|
|
|
stock.Quantity -= delta
|
|
|
|
return delta
|
|
|
|
}
|
|
|
|
|
2020-08-04 05:50:27 +00:00
|
|
|
type StockSlice []Stock
|
|
|
|
|
|
|
|
func (slice StockSlice) Quantity() (total float64) {
|
|
|
|
for _, stock := range slice {
|
|
|
|
total += stock.Quantity
|
|
|
|
}
|
|
|
|
|
|
|
|
return total
|
|
|
|
}
|
|
|
|
|
2020-08-04 01:47:54 +00:00
|
|
|
type StockManager struct {
|
2020-08-04 05:50:27 +00:00
|
|
|
Symbol string
|
2020-08-04 01:47:54 +00:00
|
|
|
TradingFeeCurrency string
|
2020-08-04 05:50:27 +00:00
|
|
|
Stocks StockSlice
|
|
|
|
PendingSells StockSlice
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *StockManager) Stock(buy Stock) error {
|
|
|
|
m.Stocks = append(m.Stocks, buy)
|
|
|
|
|
|
|
|
if len(m.PendingSells) > 0 {
|
|
|
|
pendingSells := m.PendingSells
|
|
|
|
m.PendingSells = nil
|
|
|
|
for _, sell := range pendingSells {
|
|
|
|
if err := m.Consume(sell); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-08-04 01:47:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *StockManager) Consume(sell Stock) error {
|
|
|
|
if len(m.Stocks) == 0 {
|
2020-08-04 05:50:27 +00:00
|
|
|
m.PendingSells = append(m.PendingSells, sell)
|
|
|
|
return nil
|
2020-08-04 01:47:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
idx := len(m.Stocks) - 1
|
|
|
|
for ; idx >= 0; idx-- {
|
|
|
|
stock := m.Stocks[idx]
|
|
|
|
|
|
|
|
// find any stock price is lower than the sell trade
|
|
|
|
if stock.Price >= sell.Price {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
sell.Quantity -= stock.Consume(sell.Quantity)
|
2020-08-04 05:50:27 +00:00
|
|
|
log.Infof("sell quantity: %f", sell.Quantity)
|
2020-08-04 01:47:54 +00:00
|
|
|
|
2020-08-04 05:50:27 +00:00
|
|
|
m.Stocks[idx] = stock
|
2020-08-04 01:47:54 +00:00
|
|
|
|
|
|
|
if math.Round(stock.Quantity*1e8) == 0.0 {
|
|
|
|
m.Stocks = m.Stocks[:idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
if math.Round(sell.Quantity*1e8) == 0.0 {
|
2020-08-04 05:50:27 +00:00
|
|
|
return nil
|
2020-08-04 01:47:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = len(m.Stocks) - 1
|
|
|
|
for ; idx >= 0; idx-- {
|
|
|
|
stock := m.Stocks[idx]
|
|
|
|
sell.Quantity -= stock.Consume(sell.Quantity)
|
2020-08-04 05:50:27 +00:00
|
|
|
m.Stocks[idx] = stock
|
|
|
|
|
2020-08-04 01:47:54 +00:00
|
|
|
if math.Round(stock.Quantity*1e8) == 0.0 {
|
2020-08-04 05:50:27 +00:00
|
|
|
// remove the latest stock
|
2020-08-04 01:47:54 +00:00
|
|
|
m.Stocks = m.Stocks[:idx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if math.Round(sell.Quantity*1e8) > 0.0 {
|
2020-08-04 05:50:27 +00:00
|
|
|
m.PendingSells = append(m.PendingSells, sell)
|
2020-08-04 01:47:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *StockManager) LoadTrades(trades []types.Trade) (checkpoints []int, err error) {
|
|
|
|
feeSymbol := strings.HasPrefix(m.Symbol, m.TradingFeeCurrency)
|
|
|
|
for idx, trade := range trades {
|
2020-08-04 05:50:27 +00:00
|
|
|
log.Infof("%s %5s %f %f at %s fee %s %f", trade.Symbol, trade.Side, trade.Price, trade.Quantity, trade.Time, trade.FeeCurrency, trade.Fee)
|
2020-08-04 01:47:54 +00:00
|
|
|
// for other market trades
|
|
|
|
// convert trading fee trades to sell trade
|
|
|
|
if trade.Symbol != m.Symbol {
|
|
|
|
if feeSymbol && trade.FeeCurrency == m.TradingFeeCurrency {
|
|
|
|
trade.Symbol = m.Symbol
|
|
|
|
trade.IsBuyer = false
|
|
|
|
trade.Quantity = trade.Fee
|
|
|
|
trade.Fee = 0.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if trade.Symbol == m.Symbol {
|
|
|
|
if trade.IsBuyer {
|
2020-08-04 05:50:27 +00:00
|
|
|
if idx > 0 && len(m.Stocks) == 0 {
|
|
|
|
checkpoints = append(checkpoints, idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
stock := toStock(trade)
|
|
|
|
if err := m.Stock(stock); err != nil {
|
|
|
|
return checkpoints, err
|
|
|
|
}
|
2020-08-04 01:47:54 +00:00
|
|
|
} else {
|
2020-08-04 05:50:27 +00:00
|
|
|
stock := toStock(trade)
|
|
|
|
if err := m.Consume(stock); err != nil {
|
2020-08-04 01:47:54 +00:00
|
|
|
return checkpoints, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return checkpoints, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func toStock(trade types.Trade) Stock {
|
|
|
|
if strings.HasPrefix(trade.Symbol, trade.FeeCurrency) {
|
|
|
|
if trade.IsBuyer {
|
|
|
|
trade.Quantity -= trade.Fee
|
|
|
|
} else {
|
|
|
|
trade.Quantity += trade.Fee
|
|
|
|
}
|
|
|
|
trade.Fee = 0
|
|
|
|
}
|
|
|
|
return Stock(trade)
|
|
|
|
}
|