add accumulated profit column to position

This commit is contained in:
c9s 2022-03-11 14:57:52 +08:00
parent cc4ef327d6
commit 3c376b3cd3
4 changed files with 70 additions and 26 deletions

View File

@ -64,6 +64,8 @@ func (s *PositionService) Insert(position *types.Position, trade types.Trade, pr
quote,
profit,
trade_id,
exchange,
side,
traded_at
) VALUES (
:strategy,
@ -76,17 +78,24 @@ func (s *PositionService) Insert(position *types.Position, trade types.Trade, pr
:quote,
:profit,
:trade_id,
:exchange,
:side,
:traded_at
)`,
map[string]interface{} {
"strategy": "",
"strategy_instance_id": "",
map[string]interface{}{
"strategy": position.Strategy,
"strategy_instance_id": position.StrategyInstanceID,
"symbol": position.Symbol,
"quote_currency": position.QuoteCurrency,
"base_currency": position.BaseCurrency,
"average_cost": position.AverageCost,
"base": position.Base,
"quote": position.Quote,
"profit": profit,
"trade_id": trade.ID,
"exchange": trade.Exchange,
"side": trade.Side,
"traded_at": trade.Time,
})
return err
}

View File

@ -17,18 +17,45 @@ func TestPositionService(t *testing.T) {
t.Fatal(err)
}
defer db.Close()
defer func() {
err := db.Close()
assert.NoError(t, err)
}()
xdb := sqlx.NewDb(db.DB, "sqlite3")
service := &PositionService{DB: xdb}
t.Run("minimal fields", func(t *testing.T) {
err = service.Insert(&types.Position{
Symbol: "BTCUSDT",
BaseCurrency: "BTC",
QuoteCurrency: "USDT",
AverageCost: fixedpoint.NewFromFloat(44000),
ChangedAt: time.Now(),
}, types.Trade{}, fixedpoint.NewFromFloat(10.9))
}, types.Trade{
Time: types.Time(time.Now()),
}, 0)
assert.NoError(t, err)
})
t.Run("full fields", func(t *testing.T) {
err = service.Insert(&types.Position{
Symbol: "BTCUSDT",
BaseCurrency: "BTC",
QuoteCurrency: "USDT",
AverageCost: fixedpoint.NewFromFloat(44000),
Base: fixedpoint.NewFromFloat(0.1),
Quote: fixedpoint.NewFromFloat(-44000.0),
ChangedAt: time.Now(),
Strategy: "bollmaker",
StrategyInstanceID: "bollmaker-BTCUSDT-1m",
}, types.Trade{
ID: 9,
Exchange: types.ExchangeBinance,
Side: types.SideTypeSell,
Time: types.Time(time.Now()),
}, fixedpoint.NewFromFloat(10.9))
assert.NoError(t, err)
})
}

View File

@ -30,15 +30,15 @@ type PositionRisk struct {
}
type Position struct {
Symbol string `json:"symbol"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
Symbol string `json:"symbol" db:"symbol"`
BaseCurrency string `json:"baseCurrency" db:"base"`
QuoteCurrency string `json:"quoteCurrency" db:"quote"`
Market Market `json:"market,omitempty"`
Base fixedpoint.Value `json:"base"`
Quote fixedpoint.Value `json:"quote"`
AverageCost fixedpoint.Value `json:"averageCost"`
Base fixedpoint.Value `json:"base" db:"base"`
Quote fixedpoint.Value `json:"quote" db:"quote"`
AverageCost fixedpoint.Value `json:"averageCost" db:"average_cost"`
// ApproximateAverageCost adds the computed fee in quote in the average cost
// This is used for calculating net profit
@ -48,13 +48,15 @@ type Position struct {
ExchangeFeeRates map[ExchangeName]ExchangeFee `json:"exchangeFeeRates"`
// TotalFee stores the fee currency -> total fee quantity
TotalFee map[string]fixedpoint.Value `json:"totalFee"`
TotalFee map[string]fixedpoint.Value `json:"totalFee" db:"-"`
ChangedAt time.Time `json:"changedAt,omitempty"`
ChangedAt time.Time `json:"changedAt,omitempty" db:"changed_at"`
Strategy string `json:"strategy,omitempty" db:"strategy"`
StrategyInstanceID string `json:"strategyInstanceID,omitempty" db:"strategy_instance_id"`
AccumulatedProfit fixedpoint.Value `json:"accumulatedProfit,omitempty" db:"accumulated_profit"`
sync.Mutex
}
@ -306,6 +308,7 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
fee := td.Fee
// calculated fee in quote (some exchange accounts may enable platform currency fee discount, like BNB)
// convert platform fee token into USD values
var feeInQuote fixedpoint.Value = fixedpoint.Zero
switch td.FeeCurrency {
@ -353,6 +356,7 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
p.Quote = p.Quote.Sub(quoteQuantity)
p.AverageCost = price
p.ApproximateAverageCost = price
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
} else {
// covering short position
@ -360,6 +364,7 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
p.Quote = p.Quote.Sub(quoteQuantity)
profit = p.AverageCost.Sub(price).Mul(quantity)
netProfit = p.ApproximateAverageCost.Sub(price).Mul(quantity).Sub(feeInQuote)
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
}
}
@ -385,12 +390,14 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
p.Quote = p.Quote.Add(quoteQuantity)
p.AverageCost = price
p.ApproximateAverageCost = price
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
} else {
p.Base = p.Base.Sub(quantity)
p.Quote = p.Quote.Add(quoteQuantity)
profit = price.Sub(p.AverageCost).Mul(quantity)
netProfit = price.Sub(p.ApproximateAverageCost).Mul(quantity).Sub(feeInQuote)
p.AccumulatedProfit = p.AccumulatedProfit.Add(profit)
return profit, netProfit, true
}
}

View File

@ -136,6 +136,7 @@ func convertFloat64ToTime(vt string, f float64) (time.Time, error) {
return time.Time{}, fmt.Errorf("the floating point value %f is out of the timestamp range", f)
}
// Time type implements the driver value for sqlite
type Time time.Time
var layout = "2006-01-02 15:04:05.999Z07:00"