Merge pull request #348 from austin362667/refactor-position

types: refactor Position and related files
This commit is contained in:
Yo-An Lin 2021-12-11 19:25:29 +08:00 committed by GitHub
commit 8fba55707c
11 changed files with 105 additions and 92 deletions

View File

@ -3,7 +3,6 @@ package pnl
import (
"time"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
@ -33,8 +32,8 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
var currencyFees = map[string]float64{}
var position = bbgo.NewPositionFromMarket(c.Market)
position.SetFeeRate(bbgo.ExchangeFee{
var position = types.NewPositionFromMarket(c.Market)
position.SetFeeRate(types.ExchangeFee{
// binance vip 0 uses 0.075%
MakerFeeRate: fixedpoint.NewFromFloat(0.075 * 0.01),
TakerFeeRate: fixedpoint.NewFromFloat(0.075 * 0.01),

View File

@ -199,7 +199,7 @@ type ExchangeSession struct {
// marketDataStores contains the market data store of each market
marketDataStores map[string]*MarketDataStore
positions map[string]*Position
positions map[string]*types.Position
// standard indicators of each market
standardIndicatorSets map[string]*StandardIndicatorSet
@ -236,7 +236,7 @@ func NewExchangeSession(name string, exchange types.Exchange) *ExchangeSession {
markets: make(map[string]types.Market),
startPrices: make(map[string]float64),
lastPrices: make(map[string]float64),
positions: make(map[string]*Position),
positions: make(map[string]*types.Position),
marketDataStores: make(map[string]*MarketDataStore),
standardIndicatorSets: make(map[string]*StandardIndicatorSet),
orderStores: make(map[string]*OrderStore),
@ -388,7 +388,7 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
session.Trades[symbol].Append(trade)
})
position := &Position{
position := &types.Position{
Symbol: symbol,
BaseCurrency: market.BaseCurrency,
QuoteCurrency: market.QuoteCurrency,
@ -475,7 +475,7 @@ func (session *ExchangeSession) StandardIndicatorSet(symbol string) (*StandardIn
return set, ok
}
func (session *ExchangeSession) Position(symbol string) (pos *Position, ok bool) {
func (session *ExchangeSession) Position(symbol string) (pos *types.Position, ok bool) {
pos, ok = session.positions[symbol]
if ok {
return pos, ok
@ -486,7 +486,7 @@ func (session *ExchangeSession) Position(symbol string) (pos *Position, ok bool)
return nil, false
}
pos = &Position{
pos = &types.Position{
Symbol: symbol,
BaseCurrency: market.BaseCurrency,
QuoteCurrency: market.QuoteCurrency,
@ -496,7 +496,7 @@ func (session *ExchangeSession) Position(symbol string) (pos *Position, ok bool)
return pos, ok
}
func (session *ExchangeSession) Positions() map[string]*Position {
func (session *ExchangeSession) Positions() map[string]*types.Position {
return session.positions
}
@ -712,7 +712,7 @@ func InitExchangeSession(name string, session *ExchangeSession) error {
session.lastPrices = make(map[string]float64)
session.startPrices = make(map[string]float64)
session.marketDataStores = make(map[string]*MarketDataStore)
session.positions = make(map[string]*Position)
session.positions = make(map[string]*types.Position)
session.standardIndicatorSets = make(map[string]*StandardIndicatorSet)
session.orderStores = make(map[string]*OrderStore)
session.OrderExecutor = &ExchangeOrderExecutor{

View File

@ -16,15 +16,15 @@ type TradeCollector struct {
tradeStore *TradeStore
tradeC chan types.Trade
position *Position
position *types.Position
orderStore *OrderStore
tradeCallbacks []func(trade types.Trade)
positionUpdateCallbacks []func(position *Position)
positionUpdateCallbacks []func(position *types.Position)
profitCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)
}
func NewTradeCollector(symbol string, position *Position, orderStore *OrderStore) *TradeCollector {
func NewTradeCollector(symbol string, position *types.Position, orderStore *OrderStore) *TradeCollector {
return &TradeCollector{
Symbol: symbol,
orderSig: sigchan.New(1),

View File

@ -17,11 +17,11 @@ func (c *TradeCollector) EmitTrade(trade types.Trade) {
}
}
func (c *TradeCollector) OnPositionUpdate(cb func(position *Position)) {
func (c *TradeCollector) OnPositionUpdate(cb func(position *types.Position)) {
c.positionUpdateCallbacks = append(c.positionUpdateCallbacks, cb)
}
func (c *TradeCollector) EmitPositionUpdate(position *Position) {
func (c *TradeCollector) EmitPositionUpdate(position *types.Position) {
for _, cb := range c.positionUpdateCallbacks {
cb(position)
}

View File

@ -37,7 +37,7 @@ type TwapExecution struct {
activeMakerOrders *LocalActiveOrderBook
orderStore *OrderStore
position *Position
position *types.Position
executionCtx context.Context
cancelExecution context.CancelFunc
@ -444,7 +444,7 @@ func (e *TwapExecution) Run(parentCtx context.Context) error {
e.userDataStream = e.Session.Exchange.NewStream()
e.userDataStream.OnTradeUpdate(e.handleTradeUpdate)
e.position = &Position{
e.position = &types.Position{
Symbol: e.Symbol,
BaseCurrency: e.market.BaseCurrency,
QuoteCurrency: e.market.QuoteCurrency,

View File

@ -30,7 +30,7 @@ func init() {
}
type State struct {
Position *bbgo.Position `json:"position,omitempty"`
Position *types.Position `json:"position,omitempty"`
ProfitStats bbgo.ProfitStats `json:"profitStats,omitempty"`
}
@ -119,7 +119,7 @@ func (s *Strategy) LoadState() error {
// if position is nil, we need to allocate a new position for calculation
if s.state.Position == nil {
s.state.Position = bbgo.NewPositionFromMarket(s.market)
s.state.Position = types.NewPositionFromMarket(s.market)
}
// init profit states
@ -297,7 +297,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.state.ProfitStats.AddTrade(trade)
})
s.tradeCollector.OnPositionUpdate(func(position *bbgo.Position) {
s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
log.Infof("position changed: %s", s.state.Position)
s.Notify(s.state.Position)
})

View File

@ -31,7 +31,7 @@ type State struct {
Orders []types.SubmitOrder `json:"orders,omitempty"`
FilledBuyGrids map[fixedpoint.Value]struct{} `json:"filledBuyGrids"`
FilledSellGrids map[fixedpoint.Value]struct{} `json:"filledSellGrids"`
Position *bbgo.Position `json:"position,omitempty"`
Position *types.Position `json:"position,omitempty"`
AccumulativeArbitrageProfit fixedpoint.Value `json:"accumulativeArbitrageProfit"`
@ -511,7 +511,7 @@ func (s *Strategy) LoadState() error {
FilledBuyGrids: make(map[fixedpoint.Value]struct{}),
FilledSellGrids: make(map[fixedpoint.Value]struct{}),
ArbitrageOrders: make(map[uint64]types.Order),
Position: bbgo.NewPositionFromMarket(s.Market),
Position: types.NewPositionFromMarket(s.Market),
}
} else {
s.state = &state
@ -600,7 +600,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
*/
s.tradeCollector.OnPositionUpdate(func(position *bbgo.Position) {
s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.Notifiability.Notify(position)
})
s.tradeCollector.BindStream(session.UserDataStream)

View File

@ -27,7 +27,7 @@ func init() {
}
type State struct {
Position *bbgo.Position `json:"position,omitempty"`
Position *types.Position `json:"position,omitempty"`
}
type Target struct {
@ -42,7 +42,7 @@ type PercentageTargetStop struct {
}
// GenerateOrders generates the orders from the given targets
func (stop *PercentageTargetStop) GenerateOrders(market types.Market, pos *bbgo.Position) []types.SubmitOrder {
func (stop *PercentageTargetStop) GenerateOrders(market types.Market, pos *types.Position) []types.SubmitOrder {
var price = pos.AverageCost
var quantity = pos.Base
@ -178,7 +178,7 @@ func (s *Strategy) LoadState() error {
}
if s.state.Position == nil {
s.state.Position = bbgo.NewPositionFromMarket(s.Market)
s.state.Position = types.NewPositionFromMarket(s.Market)
}
return nil

View File

@ -41,7 +41,7 @@ func init() {
type State struct {
HedgePosition fixedpoint.Value `json:"hedgePosition"`
CoveredPosition fixedpoint.Value `json:"coveredPosition,omitempty"`
Position *bbgo.Position `json:"position,omitempty"`
Position *types.Position `json:"position,omitempty"`
ProfitStats ProfitStats `json:"profitStats,omitempty"`
}
@ -680,7 +680,7 @@ func (s *Strategy) LoadState() error {
// if position is nil, we need to allocate a new position for calculation
if s.state.Position == nil {
s.state.Position = bbgo.NewPositionFromMarket(s.makerMarket)
s.state.Position = types.NewPositionFromMarket(s.makerMarket)
}
s.state.ProfitStats.Symbol = s.makerMarket.Symbol
@ -794,14 +794,14 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
}
if s.makerSession.MakerFeeRate > 0 || s.makerSession.TakerFeeRate > 0 {
s.state.Position.SetExchangeFeeRate(types.ExchangeName(s.MakerExchange), bbgo.ExchangeFee{
s.state.Position.SetExchangeFeeRate(types.ExchangeName(s.MakerExchange), types.ExchangeFee{
MakerFeeRate: s.makerSession.MakerFeeRate,
TakerFeeRate: s.makerSession.TakerFeeRate,
})
}
if s.sourceSession.MakerFeeRate > 0 || s.sourceSession.TakerFeeRate > 0 {
s.state.Position.SetExchangeFeeRate(types.ExchangeName(s.SourceExchange), bbgo.ExchangeFee{
s.state.Position.SetExchangeFeeRate(types.ExchangeName(s.SourceExchange), types.ExchangeFee{
MakerFeeRate: s.sourceSession.MakerFeeRate,
TakerFeeRate: s.sourceSession.TakerFeeRate,
})

View File

@ -1,4 +1,4 @@
package bbgo
package types
import (
"fmt"
@ -6,7 +6,6 @@ import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/slack-go/slack"
)
@ -21,7 +20,7 @@ type Position struct {
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
Market types.Market `json:"market"`
Market Market `json:"market"`
Base fixedpoint.Value `json:"base"`
Quote fixedpoint.Value `json:"quote"`
@ -32,12 +31,28 @@ type Position struct {
ApproximateAverageCost fixedpoint.Value `json:"approximateAverageCost"`
FeeRate *ExchangeFee `json:"feeRate,omitempty"`
ExchangeFeeRates map[types.ExchangeName]ExchangeFee `json:"exchangeFeeRates"`
ExchangeFeeRates map[ExchangeName]ExchangeFee `json:"exchangeFeeRates"`
// Futures data fields
Isolated bool `json:"isolated"`
Leverage fixedpoint.Value `json:"leverage"`
InitialMargin fixedpoint.Value `json:"initialMargin"`
MaintMargin fixedpoint.Value `json:"maintMargin"`
OpenOrderInitialMargin fixedpoint.Value `json:"openOrderInitialMargin"`
PositionInitialMargin fixedpoint.Value `json:"positionInitialMargin"`
UnrealizedProfit fixedpoint.Value `json:"unrealizedProfit"`
EntryPrice fixedpoint.Value `json:"entryPrice"`
MaxNotional fixedpoint.Value `json:"maxNotional"`
PositionSide string `json:"positionSide"`
PositionAmt fixedpoint.Value `json:"positionAmt"`
Notional fixedpoint.Value `json:"notional"`
IsolatedWallet fixedpoint.Value `json:"isolatedWallet"`
UpdateTime int64 `json:"updateTime"`
sync.Mutex
}
func NewPositionFromMarket(market types.Market) *Position {
func NewPositionFromMarket(market Market) *Position {
return &Position{
Symbol: market.Symbol,
BaseCurrency: market.BaseCurrency,
@ -64,9 +79,9 @@ func (p *Position) SetFeeRate(exchangeFee ExchangeFee) {
p.FeeRate = &exchangeFee
}
func (p *Position) SetExchangeFeeRate(ex types.ExchangeName, exchangeFee ExchangeFee) {
func (p *Position) SetExchangeFeeRate(ex ExchangeName, exchangeFee ExchangeFee) {
if p.ExchangeFeeRates == nil {
p.ExchangeFeeRates = make(map[types.ExchangeName]ExchangeFee)
p.ExchangeFeeRates = make(map[ExchangeName]ExchangeFee)
}
p.ExchangeFeeRates[ex] = exchangeFee
@ -127,15 +142,15 @@ func (p *Position) String() string {
)
}
func (p *Position) BindStream(stream types.Stream) {
stream.OnTradeUpdate(func(trade types.Trade) {
func (p *Position) BindStream(stream Stream) {
stream.OnTradeUpdate(func(trade Trade) {
if p.Symbol == trade.Symbol {
p.AddTrade(trade)
}
})
}
func (p *Position) AddTrades(trades []types.Trade) (fixedpoint.Value, fixedpoint.Value, bool) {
func (p *Position) AddTrades(trades []Trade) (fixedpoint.Value, fixedpoint.Value, bool) {
var totalProfitAmount, totalNetProfit fixedpoint.Value
for _, trade := range trades {
if profit, netProfit, madeProfit := p.AddTrade(trade); madeProfit {
@ -147,7 +162,7 @@ func (p *Position) AddTrades(trades []types.Trade) (fixedpoint.Value, fixedpoint
return totalProfitAmount, totalNetProfit, totalProfitAmount != 0
}
func (p *Position) AddTrade(t types.Trade) (profit fixedpoint.Value, netProfit fixedpoint.Value, madeProfit bool) {
func (p *Position) AddTrade(t Trade) (profit fixedpoint.Value, netProfit fixedpoint.Value, madeProfit bool) {
price := fixedpoint.NewFromFloat(t.Price)
quantity := fixedpoint.NewFromFloat(t.Quantity)
quoteQuantity := fixedpoint.NewFromFloat(t.QuoteQuantity)
@ -189,7 +204,7 @@ func (p *Position) AddTrade(t types.Trade) (profit fixedpoint.Value, netProfit f
// Base < 0 means we're in short position
switch t.Side {
case types.SideTypeBuy:
case SideTypeBuy:
if p.Base < 0 {
// convert short position to long position
if p.Base+quantity > 0 {
@ -217,7 +232,7 @@ func (p *Position) AddTrade(t types.Trade) (profit fixedpoint.Value, netProfit f
return 0, 0, false
case types.SideTypeSell:
case SideTypeSell:
if p.Base > 0 {
// convert long position to short position
if p.Base-quantity < 0 {

View File

@ -1,4 +1,4 @@
package bbgo
package types
import (
"testing"
@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
func TestPosition_ExchangeFeeRate_Short(t *testing.T) {
@ -17,7 +16,7 @@ func TestPosition_ExchangeFeeRate_Short(t *testing.T) {
}
feeRate := 0.075 * 0.01
pos.SetExchangeFeeRate(types.ExchangeBinance, ExchangeFee{
pos.SetExchangeFeeRate(ExchangeBinance, ExchangeFee{
MakerFeeRate: fixedpoint.NewFromFloat(feeRate),
TakerFeeRate: fixedpoint.NewFromFloat(feeRate),
})
@ -27,24 +26,24 @@ func TestPosition_ExchangeFeeRate_Short(t *testing.T) {
fee := quoteQuantity * feeRate
averageCost := (quoteQuantity - fee) / quantity
bnbPrice := 570.0
pos.AddTrade(types.Trade{
Exchange: types.ExchangeBinance,
pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 3000.0,
Quantity: quantity,
QuoteQuantity: quoteQuantity,
Symbol: "BTCUSDT",
Side: types.SideTypeSell,
Side: SideTypeSell,
Fee: fee / bnbPrice,
FeeCurrency: "BNB",
})
_, netProfit, madeProfit := pos.AddTrade(types.Trade{
Exchange: types.ExchangeBinance,
_, netProfit, madeProfit := pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 2000.0,
Quantity: 10.0,
QuoteQuantity: 2000.0 * 10.0,
Symbol: "BTCUSDT",
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Fee: 2000.0 * 10.0 * feeRate / bnbPrice,
FeeCurrency: "BNB",
})
@ -62,7 +61,7 @@ func TestPosition_ExchangeFeeRate_Long(t *testing.T) {
}
feeRate := 0.075 * 0.01
pos.SetExchangeFeeRate(types.ExchangeBinance, ExchangeFee{
pos.SetExchangeFeeRate(ExchangeBinance, ExchangeFee{
MakerFeeRate: fixedpoint.NewFromFloat(feeRate),
TakerFeeRate: fixedpoint.NewFromFloat(feeRate),
})
@ -72,24 +71,24 @@ func TestPosition_ExchangeFeeRate_Long(t *testing.T) {
fee := quoteQuantity * feeRate
averageCost := (quoteQuantity + fee) / quantity
bnbPrice := 570.0
pos.AddTrade(types.Trade{
Exchange: types.ExchangeBinance,
pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 3000.0,
Quantity: quantity,
QuoteQuantity: quoteQuantity,
Symbol: "BTCUSDT",
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Fee: fee / bnbPrice,
FeeCurrency: "BNB",
})
_, netProfit, madeProfit := pos.AddTrade(types.Trade{
Exchange: types.ExchangeBinance,
_, netProfit, madeProfit := pos.AddTrade(Trade{
Exchange: ExchangeBinance,
Price: 4000.0,
Quantity: 10.0,
QuoteQuantity: 4000.0 * 10.0,
Symbol: "BTCUSDT",
Side: types.SideTypeSell,
Side: SideTypeSell,
Fee: 4000.0 * 10.0 * feeRate / bnbPrice,
FeeCurrency: "BNB",
})
@ -103,7 +102,7 @@ func TestPosition(t *testing.T) {
var feeRate = 0.05 * 0.01
var testcases = []struct {
name string
trades []types.Trade
trades []Trade
expectedAverageCost fixedpoint.Value
expectedBase fixedpoint.Value
expectedQuote fixedpoint.Value
@ -111,9 +110,9 @@ func TestPosition(t *testing.T) {
}{
{
name: "base fee",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
@ -128,9 +127,9 @@ func TestPosition(t *testing.T) {
},
{
name: "quote fee",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeSell,
Side: SideTypeSell,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
@ -145,15 +144,15 @@ func TestPosition(t *testing.T) {
},
{
name: "long",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
},
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
@ -167,21 +166,21 @@ func TestPosition(t *testing.T) {
{
name: "long and sell",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
},
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
},
{
Side: types.SideTypeSell,
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.01,
QuoteQuantity: 3000.0 * 0.01,
@ -195,21 +194,21 @@ func TestPosition(t *testing.T) {
{
name: "long and sell to short",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 1000.0,
Quantity: 0.01,
QuoteQuantity: 1000.0 * 0.01,
},
{
Side: types.SideTypeBuy,
Side: SideTypeBuy,
Price: 2000.0,
Quantity: 0.03,
QuoteQuantity: 2000.0 * 0.03,
},
{
Side: types.SideTypeSell,
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.10,
QuoteQuantity: 3000.0 * 0.10,
@ -224,15 +223,15 @@ func TestPosition(t *testing.T) {
{
name: "short",
trades: []types.Trade{
trades: []Trade{
{
Side: types.SideTypeSell,
Side: SideTypeSell,
Price: 2000.0,
Quantity: 0.01,
QuoteQuantity: 2000.0 * 0.01,
},
{
Side: types.SideTypeSell,
Side: SideTypeSell,
Price: 3000.0,
Quantity: 0.03,
QuoteQuantity: 3000.0 * 0.03,