refactor: 逐批次实时写入DB,删除persistTrade
This commit is contained in:
@@ -63,6 +63,9 @@ type ArbPosition struct {
|
||||
// Track all entry prices for weighted-average PnL across scale-ins (Issue #2)
|
||||
LongEntryPrices []float64 // all long entry prices (initial + scale-ins)
|
||||
ShortEntryPrices []float64 // all short entry prices (initial + scale-ins)
|
||||
|
||||
// DB trade ID — set after first save, used for incremental order/scale/exit persists
|
||||
DBTradeID int64
|
||||
}
|
||||
|
||||
// DeepCopy returns a copy-safe snapshot of the position (no shared pointers).
|
||||
@@ -410,6 +413,55 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
||||
pos.LastScaleAt = time.Now()
|
||||
pos.Status = "open" // both legs placed, ready for Tick/exit logic
|
||||
|
||||
// Persist entry to DB immediately (incremental — not batch at close)
|
||||
if t.db != nil {
|
||||
now := time.Now()
|
||||
status := "filled"
|
||||
tradeUnit := t.cfg.TradeAmountUSD
|
||||
es := pos.EntrySpread
|
||||
|
||||
dbTrade := &db.TradeRecord{
|
||||
Coin: pos.Coin,
|
||||
Direction: pos.Direction,
|
||||
Status: "open",
|
||||
EntrySpread: &es,
|
||||
LongExchange: pos.LongLeg.Exchange,
|
||||
ShortExchange: pos.ShortLeg.Exchange,
|
||||
LongEntry: &pos.LongLeg.EntryPrice,
|
||||
ShortEntry: &pos.ShortLeg.EntryPrice,
|
||||
AmountUSD: t.cfg.TradeAmountUSD,
|
||||
OpenedAt: now,
|
||||
}
|
||||
if tradeID, err := t.db.SaveTrade(dbTrade); err == nil {
|
||||
pos.DBTradeID = tradeID
|
||||
|
||||
longFee := tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||
shortFee := tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
longShares := tradeUnit / pos.LongLeg.EntryPrice
|
||||
shortShares := tradeUnit / pos.ShortLeg.EntryPrice
|
||||
|
||||
longOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID, Leg: "long", Type: "entry",
|
||||
Exchange: pos.LongLeg.Exchange, Side: "buy",
|
||||
Price: &pos.LongLeg.EntryPrice, Size: &longShares,
|
||||
Fee: &longFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID, Leg: "short", Type: "entry",
|
||||
Exchange: pos.ShortLeg.Exchange, Side: "sell",
|
||||
Price: &pos.ShortLeg.EntryPrice, Size: &shortShares,
|
||||
Fee: &shortFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: tradeID, Type: "entry", Status: "filled",
|
||||
Spread: &es,
|
||||
LongPrice: &pos.LongLeg.EntryPrice, ShortPrice: &pos.ShortLeg.EntryPrice,
|
||||
LongOrderID: &longOID, ShortOrderID: &shortOID,
|
||||
CreatedAt: now,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[Trader] %s: Opened %s | Long %s @ %.2f Short %s @ %.2f | $%.0f",
|
||||
pos.Coin, pos.Direction, pos.LongLeg.Exchange, pos.LongLeg.EntryPrice,
|
||||
pos.ShortLeg.Exchange, pos.ShortLeg.EntryPrice, t.cfg.TradeAmountUSD)
|
||||
@@ -495,6 +547,39 @@ func (t *Trader) checkScaleIn(pos *ArbPosition, bgP, hlP, diffPct float64, store
|
||||
pos.LongEntryPrices = append(pos.LongEntryPrices, longPrice)
|
||||
pos.ShortEntryPrices = append(pos.ShortEntryPrices, shortPrice)
|
||||
|
||||
// Persist scale orders to DB immediately
|
||||
if t.db != nil && pos.DBTradeID > 0 {
|
||||
now := time.Now()
|
||||
status := "filled"
|
||||
tradeUnit := t.cfg.TradeAmountUSD
|
||||
es := pos.EntrySpread
|
||||
|
||||
longFee := tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||
shortFee := tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
longShares := tradeUnit / longPrice
|
||||
shortShares := tradeUnit / shortPrice
|
||||
|
||||
longOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "long", Type: "scale",
|
||||
Exchange: pos.LongLeg.Exchange, Side: "buy",
|
||||
Price: &longPrice, Size: &longShares,
|
||||
Fee: &longFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "short", Type: "scale",
|
||||
Exchange: pos.ShortLeg.Exchange, Side: "sell",
|
||||
Price: &shortPrice, Size: &shortShares,
|
||||
Fee: &shortFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: pos.DBTradeID, Type: "scale", Status: "filled",
|
||||
Spread: &es,
|
||||
LongPrice: &longPrice, ShortPrice: &shortPrice,
|
||||
LongOrderID: &longOID, ShortOrderID: &shortOID,
|
||||
CreatedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("[Trader] %s: Scale-in #%d executed | spread=%.4f%% (entry=%.4f%%) | total=$%.0f",
|
||||
pos.Coin, pos.ScaleLevels, diffPct, entryDiff, pos.AmountUSD)
|
||||
}
|
||||
@@ -617,9 +702,73 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
|
||||
t.closedTrades = append(t.closedTrades, record)
|
||||
t.mu.Unlock()
|
||||
|
||||
// Persist to SQLite
|
||||
if t.db != nil {
|
||||
t.persistTrade(pos, diffPct, convergenceLabel, exitReason, netPnl, longPnl, shortPnl, totalFees)
|
||||
// Persist exit orders + close trade in DB
|
||||
if t.db != nil && pos.DBTradeID > 0 {
|
||||
now := time.Now()
|
||||
status := "filled"
|
||||
tradeUnit := t.cfg.TradeAmountUSD
|
||||
|
||||
totalLongShares := 0.0
|
||||
for _, p := range pos.LongEntryPrices {
|
||||
totalLongShares += tradeUnit / p
|
||||
}
|
||||
totalShortShares := 0.0
|
||||
for _, p := range pos.ShortEntryPrices {
|
||||
totalShortShares += tradeUnit / p
|
||||
}
|
||||
|
||||
// Save exit orders
|
||||
longExitFee := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
|
||||
longExitShares := totalLongShares
|
||||
longOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "long", Type: "exit",
|
||||
Exchange: pos.LongLeg.Exchange, Side: "sell",
|
||||
Price: &pos.LongLeg.ExitPrice, Size: &longExitShares,
|
||||
Fee: &longExitFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
shortExitFee := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
shortExitShares := totalShortShares
|
||||
shortOID, _ := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
||||
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
||||
Price: &pos.ShortLeg.ExitPrice, Size: &shortExitShares,
|
||||
Fee: &shortExitFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
// Save exit system order
|
||||
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: pos.DBTradeID, Type: "exit", Status: "filled",
|
||||
Spread: &diffPct,
|
||||
LongPrice: &pos.LongLeg.ExitPrice, ShortPrice: &pos.ShortLeg.ExitPrice,
|
||||
LongOrderID: &longOID, ShortOrderID: &shortOID,
|
||||
CreatedAt: now,
|
||||
})
|
||||
|
||||
// Close trade: sum fees from in-memory calculation, update status
|
||||
feeEntrySum, feeExitSum := 0.0, 0.0
|
||||
for range pos.LongEntryPrices {
|
||||
feeEntrySum += tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||
}
|
||||
for range pos.ShortEntryPrices {
|
||||
feeEntrySum += tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
}
|
||||
feeExitSum = longExitFee + shortExitFee
|
||||
|
||||
t.db.UpdateTradeStatus(pos.DBTradeID, &db.TradeRecord{
|
||||
Status: "closed",
|
||||
ExitSpread: &diffPct,
|
||||
LongExit: &pos.LongLeg.ExitPrice,
|
||||
ShortExit: &pos.ShortLeg.ExitPrice,
|
||||
LongPnl: &longPnl,
|
||||
ShortPnl: &shortPnl,
|
||||
FeeEntry: &feeEntrySum,
|
||||
FeeExit: &feeExitSum,
|
||||
NetPnl: &netPnl,
|
||||
AmountUSD: pos.AmountUSD,
|
||||
ScaleCount: pos.ScaleLevels,
|
||||
ExitReason: &exitReason,
|
||||
Convergence: &convergenceLabel,
|
||||
ClosedAt: &now,
|
||||
})
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
@@ -732,7 +881,7 @@ func (t *Trader) retryClose(pos *ArbPosition, bgP, hlP float64, notifier *Notifi
|
||||
|
||||
closeErr := t.closeBothLegs(pos)
|
||||
if closeErr == "" {
|
||||
// All legs finally closed — record + persist
|
||||
// All legs finally closed — record + update DB
|
||||
pos.Status = "closed"
|
||||
pos.ExitedAt = time.Now()
|
||||
|
||||
@@ -758,9 +907,68 @@ func (t *Trader) retryClose(pos *ArbPosition, bgP, hlP float64, notifier *Notifi
|
||||
t.closedTrades = append(t.closedTrades, record)
|
||||
t.mu.Unlock()
|
||||
|
||||
if t.db != nil {
|
||||
t.persistTrade(pos, pos.ExitDiffPct, pos.ExitConvergence, pos.ExitReasonText,
|
||||
pos.ExitNetPnl, pos.ExitLongPnl, pos.ExitShortPnl, pos.ExitTotalFees)
|
||||
// Persist exit orders + close trade in DB (only for legs that weren't already closed)
|
||||
if t.db != nil && pos.DBTradeID > 0 {
|
||||
now := time.Now()
|
||||
status := "filled"
|
||||
tradeUnit := t.cfg.TradeAmountUSD
|
||||
|
||||
totalLongShares := 0.0
|
||||
for _, p := range pos.LongEntryPrices {
|
||||
totalLongShares += tradeUnit / p
|
||||
}
|
||||
totalShortShares := 0.0
|
||||
for _, p := range pos.ShortEntryPrices {
|
||||
totalShortShares += tradeUnit / p
|
||||
}
|
||||
|
||||
// Save exit orders for legs that were just now closed
|
||||
if pos.LongLeg.Closed {
|
||||
longExitFee := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
|
||||
longExitShares := totalLongShares
|
||||
_, _ = t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "long", Type: "exit",
|
||||
Exchange: pos.LongLeg.Exchange, Side: "sell",
|
||||
Price: &pos.LongLeg.ExitPrice, Size: &longExitShares,
|
||||
Fee: &longExitFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
}
|
||||
if pos.ShortLeg.Closed {
|
||||
shortExitFee := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
shortExitShares := totalShortShares
|
||||
_, _ = t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: pos.DBTradeID, Leg: "short", Type: "exit",
|
||||
Exchange: pos.ShortLeg.Exchange, Side: "buy",
|
||||
Price: &pos.ShortLeg.ExitPrice, Size: &shortExitShares,
|
||||
Fee: &shortExitFee, Status: &status, CreatedAt: now,
|
||||
})
|
||||
}
|
||||
// Save exit system order (idempotent-safe since we always overwrite on retry)
|
||||
t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: pos.DBTradeID, Type: "exit", Status: "filled",
|
||||
Spread: &pos.ExitDiffPct,
|
||||
LongPrice: &pos.LongLeg.ExitPrice, ShortPrice: &pos.ShortLeg.ExitPrice,
|
||||
CreatedAt: now,
|
||||
})
|
||||
|
||||
// Close trade using previously saved exit metadata
|
||||
feePct := pos.ExitTotalFees
|
||||
t.db.UpdateTradeStatus(pos.DBTradeID, &db.TradeRecord{
|
||||
Status: "closed",
|
||||
ExitSpread: &pos.ExitDiffPct,
|
||||
LongExit: &pos.LongLeg.ExitPrice,
|
||||
ShortExit: &pos.ShortLeg.ExitPrice,
|
||||
LongPnl: &pos.ExitLongPnl,
|
||||
ShortPnl: &pos.ExitShortPnl,
|
||||
FeeEntry: &feePct,
|
||||
FeeExit: &feePct,
|
||||
NetPnl: &pos.ExitNetPnl,
|
||||
AmountUSD: pos.AmountUSD,
|
||||
ScaleCount: pos.ScaleLevels,
|
||||
ExitReason: &pos.ExitReasonText,
|
||||
Convergence: &pos.ExitConvergence,
|
||||
ClosedAt: &now,
|
||||
})
|
||||
}
|
||||
|
||||
notifier.Send(fmt.Sprintf(
|
||||
@@ -926,200 +1134,6 @@ func (t *Trader) GetClosedTrades() []TradeRecord {
|
||||
}
|
||||
|
||||
// persistTrade saves a completed trade to SQLite, with per-leg orders and system_orders.
|
||||
func (t *Trader) persistTrade(pos *ArbPosition, exitSpread float64, convergence, exitReason string, netPnl, longPnl, shortPnl, totalFees float64) {
|
||||
var entrySpread float64
|
||||
if pos.LongLeg != nil {
|
||||
entrySpread = pos.EntrySpread
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
tradeUnit := t.cfg.TradeAmountUSD
|
||||
|
||||
// Pre-calculate all fees BEFORE saving the trade
|
||||
totalFeeEntryUSD := 0.0
|
||||
for range pos.LongEntryPrices {
|
||||
totalFeeEntryUSD += tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||
}
|
||||
for range pos.ShortEntryPrices {
|
||||
totalFeeEntryUSD += tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
}
|
||||
totalLongShares := 0.0
|
||||
for _, p := range pos.LongEntryPrices {
|
||||
totalLongShares += tradeUnit / p
|
||||
}
|
||||
totalShortShares := 0.0
|
||||
for _, p := range pos.ShortEntryPrices {
|
||||
totalShortShares += tradeUnit / p
|
||||
}
|
||||
totalFeeExitUSD := totalLongShares*pos.LongLeg.ExitPrice*takerFees[pos.LongLeg.Exchange]/100 +
|
||||
totalShortShares*pos.ShortLeg.ExitPrice*takerFees[pos.ShortLeg.Exchange]/100
|
||||
|
||||
dbTrade := &db.TradeRecord{
|
||||
Coin: pos.Coin,
|
||||
Direction: pos.Direction,
|
||||
Status: "closed",
|
||||
EntrySpread: &entrySpread,
|
||||
ExitSpread: &exitSpread,
|
||||
LongExchange: pos.LongLeg.Exchange,
|
||||
ShortExchange: pos.ShortLeg.Exchange,
|
||||
LongEntry: &pos.LongLeg.EntryPrice,
|
||||
LongExit: &pos.LongLeg.ExitPrice,
|
||||
ShortEntry: &pos.ShortLeg.EntryPrice,
|
||||
ShortExit: &pos.ShortLeg.ExitPrice,
|
||||
LongPnl: &longPnl,
|
||||
ShortPnl: &shortPnl,
|
||||
FeeEntry: &totalFeeEntryUSD,
|
||||
FeeExit: &totalFeeExitUSD,
|
||||
NetPnl: &netPnl,
|
||||
AmountUSD: pos.AmountUSD,
|
||||
ScaleCount: pos.ScaleLevels,
|
||||
ExitReason: &exitReason,
|
||||
Convergence: &convergence,
|
||||
OpenedAt: pos.StartedAt,
|
||||
ClosedAt: &now,
|
||||
}
|
||||
tradeID, err := t.db.SaveTrade(dbTrade)
|
||||
if err != nil {
|
||||
log.Printf("[Trader] Failed to save trade to DB: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save per-leg order records
|
||||
// Long leg: entry (buy), scales (buy), exit (sell)
|
||||
status := "filled"
|
||||
var longEntryOrderIDs []int64
|
||||
for i, p := range pos.LongEntryPrices {
|
||||
shares := tradeUnit / p
|
||||
orderType := "entry"
|
||||
if i > 0 {
|
||||
orderType = "scale"
|
||||
}
|
||||
fee := tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
|
||||
oid, oErr := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID,
|
||||
Leg: "long",
|
||||
Type: orderType,
|
||||
Exchange: pos.LongLeg.Exchange,
|
||||
Side: "buy",
|
||||
Price: &p,
|
||||
Size: &shares,
|
||||
Fee: &fee,
|
||||
Status: &status,
|
||||
CreatedAt: pos.StartedAt,
|
||||
})
|
||||
if oErr != nil {
|
||||
log.Printf("[Trader] Failed to save long entry order: %v", oErr)
|
||||
} else {
|
||||
longEntryOrderIDs = append(longEntryOrderIDs, oid)
|
||||
}
|
||||
}
|
||||
// Long exit (sell)
|
||||
longExitShares := totalLongShares
|
||||
longExitFee := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
|
||||
status = "filled"
|
||||
var longExitOrderID int64
|
||||
if oid, oErr := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID,
|
||||
Leg: "long",
|
||||
Type: "exit",
|
||||
Exchange: pos.LongLeg.Exchange,
|
||||
Side: "sell",
|
||||
Price: &pos.LongLeg.ExitPrice,
|
||||
Size: &longExitShares,
|
||||
Fee: &longExitFee,
|
||||
Status: &status,
|
||||
CreatedAt: now,
|
||||
}); oErr != nil {
|
||||
log.Printf("[Trader] Failed to save long exit order: %v", oErr)
|
||||
} else {
|
||||
longExitOrderID = oid
|
||||
}
|
||||
|
||||
// Short leg: entry (sell), scales (sell), exit (buy)
|
||||
var shortEntryOrderIDs []int64
|
||||
for i, p := range pos.ShortEntryPrices {
|
||||
shares := tradeUnit / p
|
||||
orderType := "entry"
|
||||
if i > 0 {
|
||||
orderType = "scale"
|
||||
}
|
||||
fee := tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
oid, oErr := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID,
|
||||
Leg: "short",
|
||||
Type: orderType,
|
||||
Exchange: pos.ShortLeg.Exchange,
|
||||
Side: "sell",
|
||||
Price: &p,
|
||||
Size: &shares,
|
||||
Fee: &fee,
|
||||
Status: &status,
|
||||
CreatedAt: pos.StartedAt,
|
||||
})
|
||||
if oErr != nil {
|
||||
log.Printf("[Trader] Failed to save short entry order: %v", oErr)
|
||||
} else {
|
||||
shortEntryOrderIDs = append(shortEntryOrderIDs, oid)
|
||||
}
|
||||
}
|
||||
// Short exit (buy)
|
||||
shortExitShares := totalShortShares
|
||||
shortExitFee := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
|
||||
var shortExitOrderID int64
|
||||
if oid, oErr := t.db.SaveOrder(&db.OrderRecord{
|
||||
TradeID: tradeID,
|
||||
Leg: "short",
|
||||
Type: "exit",
|
||||
Exchange: pos.ShortLeg.Exchange,
|
||||
Side: "buy",
|
||||
Price: &pos.ShortLeg.ExitPrice,
|
||||
Size: &shortExitShares,
|
||||
Fee: &shortExitFee,
|
||||
Status: &status,
|
||||
CreatedAt: now,
|
||||
}); oErr != nil {
|
||||
log.Printf("[Trader] Failed to save short exit order: %v", oErr)
|
||||
} else {
|
||||
shortExitOrderID = oid
|
||||
}
|
||||
|
||||
// Save system orders linking long+short legs
|
||||
es := pos.EntrySpread
|
||||
for i := 0; i < len(longEntryOrderIDs) && i < len(shortEntryOrderIDs); i++ {
|
||||
sysType := "entry"
|
||||
if i > 0 {
|
||||
sysType = "scale"
|
||||
}
|
||||
if _, sErr := t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: tradeID,
|
||||
Type: sysType,
|
||||
Status: "filled",
|
||||
Spread: &es,
|
||||
LongPrice: &pos.LongEntryPrices[i],
|
||||
ShortPrice: &pos.ShortEntryPrices[i],
|
||||
LongOrderID: &longEntryOrderIDs[i],
|
||||
ShortOrderID: &shortEntryOrderIDs[i],
|
||||
CreatedAt: pos.StartedAt,
|
||||
}); sErr != nil {
|
||||
log.Printf("[Trader] Failed to save entry system order: %v", sErr)
|
||||
}
|
||||
}
|
||||
// Exit system order
|
||||
if _, sErr := t.db.SaveSystemOrder(&db.SystemOrderRecord{
|
||||
TradeID: tradeID,
|
||||
Type: "exit",
|
||||
Status: "filled",
|
||||
Spread: &exitSpread,
|
||||
LongPrice: &pos.LongLeg.ExitPrice,
|
||||
ShortPrice: &pos.ShortLeg.ExitPrice,
|
||||
LongOrderID: &longExitOrderID,
|
||||
ShortOrderID: &shortExitOrderID,
|
||||
CreatedAt: now,
|
||||
}); sErr != nil {
|
||||
log.Printf("[Trader] Failed to save exit system order: %v", sErr)
|
||||
}
|
||||
}
|
||||
|
||||
// restoreOpenPositions loads open trades from DB and recreates their positions.
|
||||
func (t *Trader) restoreOpenPositions() {
|
||||
openTrades, err := t.db.GetOpenTrades()
|
||||
|
||||
Reference in New Issue
Block a user