mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55:15 +00:00
fix: drift exit condition, trade_stats serialization in redis
This commit is contained in:
parent
a5039de6aa
commit
a8fe20ae3a
|
@ -1,8 +1,14 @@
|
||||||
---
|
---
|
||||||
|
persistence:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
db: 0
|
||||||
|
|
||||||
sessions:
|
sessions:
|
||||||
binance:
|
binance:
|
||||||
exchange: binance
|
exchange: binance
|
||||||
futures: true
|
futures: false
|
||||||
envVarPrefix: binance
|
envVarPrefix: binance
|
||||||
heikinAshi: false
|
heikinAshi: false
|
||||||
|
|
||||||
|
@ -15,13 +21,15 @@ exchangeStrategies:
|
||||||
# kline interval for indicators
|
# kline interval for indicators
|
||||||
interval: 15m
|
interval: 15m
|
||||||
window: 2
|
window: 2
|
||||||
stoploss: 3%
|
stoploss: 2%
|
||||||
source: close
|
source: close
|
||||||
predictOffset: 14
|
predictOffset: 3
|
||||||
|
# position avg +- takeProfitFactor * atr as take profit price
|
||||||
|
takeProfitFactor: 1
|
||||||
noStopPrice: true
|
noStopPrice: true
|
||||||
noTrailingStopLoss: false
|
noTrailingStopLoss: false
|
||||||
# stddev on high/low-source
|
# stddev on high/low-source
|
||||||
hlVarianceMultiplier: 0.35
|
hlVarianceMultiplier: 0.34
|
||||||
|
|
||||||
generateGraph: true
|
generateGraph: true
|
||||||
graphPNLDeductFee: false
|
graphPNLDeductFee: false
|
||||||
|
|
|
@ -57,6 +57,7 @@ type Strategy struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
|
TakeProfitFactor float64 `json:"takeProfitFactor"`
|
||||||
StopLoss fixedpoint.Value `json:"stoploss"`
|
StopLoss fixedpoint.Value `json:"stoploss"`
|
||||||
CanvasPath string `json:"canvasPath"`
|
CanvasPath string `json:"canvasPath"`
|
||||||
PredictOffset int `json:"predictOffset"`
|
PredictOffset int `json:"predictOffset"`
|
||||||
|
@ -72,7 +73,7 @@ type Strategy struct {
|
||||||
// Whether to generate graph when shutdown
|
// Whether to generate graph when shutdown
|
||||||
GenerateGraph bool `json:"generateGraph"`
|
GenerateGraph bool `json:"generateGraph"`
|
||||||
|
|
||||||
StopOrders map[uint64]types.SubmitOrder
|
StopOrders map[uint64]*types.SubmitOrder
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
Session *bbgo.ExchangeSession
|
Session *bbgo.ExchangeSession
|
||||||
|
@ -91,6 +92,7 @@ func (s *Strategy) Print(o *os.File) {
|
||||||
hiyellow(f, "canvasPath: %s\n", s.CanvasPath)
|
hiyellow(f, "canvasPath: %s\n", s.CanvasPath)
|
||||||
hiyellow(f, "source: %s\n", s.Source)
|
hiyellow(f, "source: %s\n", s.Source)
|
||||||
hiyellow(f, "stoploss: %v\n", s.StopLoss)
|
hiyellow(f, "stoploss: %v\n", s.StopLoss)
|
||||||
|
hiyellow(f, "takeProfitFactor: %f\n", s.TakeProfitFactor)
|
||||||
hiyellow(f, "predictOffset: %d\n", s.PredictOffset)
|
hiyellow(f, "predictOffset: %d\n", s.PredictOffset)
|
||||||
hiyellow(f, "exits:\n %s\n", string(b))
|
hiyellow(f, "exits:\n %s\n", string(b))
|
||||||
hiyellow(f, "symbol: %s\n", s.Symbol)
|
hiyellow(f, "symbol: %s\n", s.Symbol)
|
||||||
|
@ -124,12 +126,23 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
s.ExitMethods.SetAndSubscribe(session, s)
|
s.ExitMethods.SetAndSubscribe(session, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
func (s *Strategy) CurrentPosition() *types.Position {
|
||||||
// Cleanup pending StopOrders
|
return s.Position
|
||||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
}
|
||||||
order := s.Position.NewMarketCloseOrder(fixedpoint.One)
|
|
||||||
|
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
|
||||||
|
order := s.Position.NewMarketCloseOrder(percentage)
|
||||||
if order == nil {
|
if order == nil {
|
||||||
return nil, false
|
return nil
|
||||||
|
}
|
||||||
|
if percentage.Compare(fixedpoint.One) == 0 {
|
||||||
|
// Cleanup pending StopOrders
|
||||||
|
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||||
|
} else {
|
||||||
|
// Should only have one stop order
|
||||||
|
for _, o := range s.StopOrders {
|
||||||
|
o.Quantity = o.Quantity.Mul(fixedpoint.One.Sub(percentage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
order.Tag = "close"
|
order.Tag = "close"
|
||||||
order.TimeInForce = ""
|
order.TimeInForce = ""
|
||||||
|
@ -146,14 +159,14 @@ func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if s.Market.IsDustQuantity(order.Quantity, price) {
|
if s.Market.IsDustQuantity(order.Quantity, price) {
|
||||||
return nil, true
|
return nil
|
||||||
}
|
}
|
||||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order)
|
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
order.Quantity = order.Quantity.Mul(fixedpoint.One.Sub(Delta))
|
order.Quantity = order.Quantity.Mul(fixedpoint.One.Sub(Delta))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return &createdOrders[0], true
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +203,7 @@ func (s *Strategy) SourceFuncGenerator() SourceFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) BindStopLoss(ctx context.Context) {
|
func (s *Strategy) BindStopLoss(ctx context.Context) {
|
||||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||||
s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
||||||
if len(s.StopOrders) == 0 {
|
if len(s.StopOrders) == 0 {
|
||||||
return
|
return
|
||||||
|
@ -222,7 +235,7 @@ func (s *Strategy) BindStopLoss(ctx context.Context) {
|
||||||
}
|
}
|
||||||
o.Quantity = baseBalance.Available
|
o.Quantity = baseBalance.Available
|
||||||
}
|
}
|
||||||
if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, o); err != nil {
|
if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *o); err != nil {
|
||||||
log.WithError(err).Errorf("cannot send stop order: %v", order)
|
log.WithError(err).Errorf("cannot send stop order: %v", order)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,16 +308,16 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
||||||
atr = s.atr.Last()
|
atr = s.atr.Last()
|
||||||
avg = s.Position.AverageCost.Float64()
|
avg = s.Position.AverageCost.Float64()
|
||||||
stoploss = s.StopLoss.Float64()
|
stoploss = s.StopLoss.Float64()
|
||||||
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef) &&
|
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||||
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef) &&
|
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
(!s.Position.IsLong() && !s.Position.IsDust(price))
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = s.ClosePosition(ctx)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -360,11 +373,12 @@ func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit ty
|
||||||
}
|
}
|
||||||
f, err = os.Create(s.GraphPNLPath)
|
f, err = os.Create(s.GraphPNLPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("open pnl")
|
log.WithError(err).Errorf("open pnl")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
panic("render pnl")
|
log.WithError(err).Errorf("render pnl")
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas = types.NewCanvas(s.InstanceID())
|
canvas = types.NewCanvas(s.InstanceID())
|
||||||
|
@ -375,11 +389,12 @@ func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit ty
|
||||||
}
|
}
|
||||||
f, err = os.Create(s.GraphCumPNLPath)
|
f, err = os.Create(s.GraphCumPNLPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("open cumpnl")
|
log.WithError(err).Errorf("open cumpnl")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
panic("render cumpnl")
|
log.WithError(err).Errorf("render cumpnl")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +425,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
s.OnEmergencyStop(func() {
|
s.OnEmergencyStop(func() {
|
||||||
_ = s.GeneralOrderExecutor.GracefulCancel(ctx)
|
_ = s.GeneralOrderExecutor.GracefulCancel(ctx)
|
||||||
_, _ = s.ClosePosition(ctx)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.GeneralOrderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
s.GeneralOrderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||||
|
@ -544,16 +559,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
highf := math.Max(kline.High.Float64(), pricef)
|
highf := math.Max(kline.High.Float64(), pricef)
|
||||||
avg := s.Position.AverageCost.Float64()
|
avg := s.Position.AverageCost.Float64()
|
||||||
|
|
||||||
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf) &&
|
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||||
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf) &&
|
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
(s.Position.IsLong() && !s.Position.IsDust(price))
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = s.ClosePosition(ctx)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -578,19 +593,26 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.stdevHigh.Update(highdiff)
|
s.stdevHigh.Update(highdiff)
|
||||||
avg := s.Position.AverageCost.Float64()
|
avg := s.Position.AverageCost.Float64()
|
||||||
|
|
||||||
|
if !s.IsBackTesting() {
|
||||||
|
balances := s.Session.GetAccount().Balances()
|
||||||
|
log.Infof("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
|
||||||
|
sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
|
||||||
|
log.Infof("balances: [Base] %v [Quote] %v", balances[s.Market.BaseCurrency], balances[s.Market.QuoteCurrency])
|
||||||
|
}
|
||||||
|
|
||||||
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
||||||
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
||||||
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf) &&
|
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition
|
(s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition
|
||||||
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf) &&
|
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
(s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
||||||
|
|
||||||
if exitShortCondition || exitLongCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = s.ClosePosition(ctx)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
}
|
}
|
||||||
if shortCondition {
|
if shortCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -611,7 +633,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Cleanup pending StopOrders
|
// Cleanup pending StopOrders
|
||||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||||
quantity := baseBalance.Available
|
quantity := baseBalance.Available
|
||||||
stopPrice := fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss)))
|
stopPrice := fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss)))
|
||||||
stopOrder := types.SubmitOrder{
|
stopOrder := types.SubmitOrder{
|
||||||
|
@ -642,7 +664,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.StopOrders[createdOrders[0].OrderID] = stopOrder
|
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
||||||
}
|
}
|
||||||
if longCondition {
|
if longCondition {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
|
@ -663,7 +685,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Cleanup pending StopOrders
|
// Cleanup pending StopOrders
|
||||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||||
quantity := quoteBalance.Available.Div(source)
|
quantity := quoteBalance.Available.Div(source)
|
||||||
stopPrice := fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss)))
|
stopPrice := fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss)))
|
||||||
stopOrder := types.SubmitOrder{
|
stopOrder := types.SubmitOrder{
|
||||||
|
@ -695,7 +717,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.StopOrders[createdOrders[0].OrderID] = stopOrder
|
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -10,9 +9,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntervalProfitCollector struct {
|
type IntervalProfitCollector struct {
|
||||||
Interval Interval
|
Interval Interval `json:"interval"`
|
||||||
Profits *Float64Slice
|
Profits *Float64Slice `json:"profits"`
|
||||||
tmpTime time.Time
|
tmpTime time.Time `json:"tmpTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
|
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
|
||||||
|
@ -92,16 +91,6 @@ func (s IntervalProfitCollector) MarshalYAML() (interface{}, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntervalProfitCollector) MarshalJSON() ([]byte, error) {
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
result["Sharpe Ratio"] = s.GetSharpe()
|
|
||||||
result["Omega Ratio"] = s.GetOmega()
|
|
||||||
result["Profitable Count"] = s.GetNumOfProfitableIntervals()
|
|
||||||
result["NonProfitable Count"] = s.GetNumOfNonProfitableIntervals()
|
|
||||||
return json.Marshal(result)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more stats from the reference:
|
// TODO: Add more stats from the reference:
|
||||||
// See https://www.metatrader5.com/en/terminal/help/algotrading/testing_report
|
// See https://www.metatrader5.com/en/terminal/help/algotrading/testing_report
|
||||||
type TradeStats struct {
|
type TradeStats struct {
|
||||||
|
@ -117,7 +106,7 @@ type TradeStats struct {
|
||||||
MostLossTrade fixedpoint.Value `json:"mostLossTrade" yaml:"mostLossTrade"`
|
MostLossTrade fixedpoint.Value `json:"mostLossTrade" yaml:"mostLossTrade"`
|
||||||
ProfitFactor fixedpoint.Value `json:"profitFactor" yaml:"profitFactor"`
|
ProfitFactor fixedpoint.Value `json:"profitFactor" yaml:"profitFactor"`
|
||||||
TotalNetProfit fixedpoint.Value `json:"totalNetProfit" yaml:"totalNetProfit"`
|
TotalNetProfit fixedpoint.Value `json:"totalNetProfit" yaml:"totalNetProfit"`
|
||||||
IntervalProfits map[Interval]*IntervalProfitCollector `json:"intervalProfits,omitempty" yaml: "intervalProfits,omitempty"`
|
IntervalProfits map[Interval]*IntervalProfitCollector `jons:"intervalProfits,omitempty" yaml: "intervalProfits,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTradeStats(symbol string) *TradeStats {
|
func NewTradeStats(symbol string) *TradeStats {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user