diff --git a/backtest/runner.go b/backtest/runner.go index 8820d9944e..9e29ba5346 100644 --- a/backtest/runner.go +++ b/backtest/runner.go @@ -503,18 +503,19 @@ func (r *Runner) buildDecisionContext(ts int64, marketData map[string]*market.Da runtime := int((ts - int64(r.cfg.StartTS*1000)) / 60000) ctx := &decision.Context{ - CurrentTime: time.UnixMilli(ts).UTC().Format("2006-01-02 15:04:05 UTC"), - RuntimeMinutes: runtime, - CallCount: callCount, - Account: accountInfo, - Positions: positions, - CandidateCoins: candidateCoins, - PromptVariant: r.cfg.PromptVariant, - MarketDataMap: marketData, - MultiTFMarket: multiTF, - BTCETHLeverage: r.cfg.Leverage.BTCETHLeverage, - AltcoinLeverage: r.cfg.Leverage.AltcoinLeverage, - Timeframes: r.cfg.Timeframes, + CurrentTime: time.UnixMilli(ts).UTC().Format("2006-01-02 15:04:05 UTC"), + CurrentTimestampMs: ts, // Set current backtest timestamp for accurate holding duration calculation + RuntimeMinutes: runtime, + CallCount: callCount, + Account: accountInfo, + Positions: positions, + CandidateCoins: candidateCoins, + PromptVariant: r.cfg.PromptVariant, + MarketDataMap: marketData, + MultiTFMarket: multiTF, + BTCETHLeverage: r.cfg.Leverage.BTCETHLeverage, + AltcoinLeverage: r.cfg.Leverage.AltcoinLeverage, + Timeframes: r.cfg.Timeframes, } // Fetch quantitative data if enabled in strategy (uses current data as approximation) @@ -847,7 +848,7 @@ func (r *Runner) convertPositions(priceMap map[string]float64) []decision.Positi UnrealizedPnLPct: 0, LiquidationPrice: pos.LiquidationPrice, MarginUsed: pos.Margin, - UpdateTime: time.Now().UnixMilli(), + UpdateTime: pos.OpenTime, // Use position open time instead of current system time }) } return list diff --git a/decision/engine.go b/decision/engine.go index 12c7305f4d..4e7ecf1d1e 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -106,23 +106,24 @@ type RecentOrder struct { // Context trading context (complete information passed to AI) type Context struct { - CurrentTime string `json:"current_time"` - RuntimeMinutes int `json:"runtime_minutes"` - CallCount int `json:"call_count"` - Account AccountInfo `json:"account"` - Positions []PositionInfo `json:"positions"` - CandidateCoins []CandidateCoin `json:"candidate_coins"` - PromptVariant string `json:"prompt_variant,omitempty"` - TradingStats *TradingStats `json:"trading_stats,omitempty"` - RecentOrders []RecentOrder `json:"recent_orders,omitempty"` - MarketDataMap map[string]*market.Data `json:"-"` - MultiTFMarket map[string]map[string]*market.Data `json:"-"` - OITopDataMap map[string]*OITopData `json:"-"` - QuantDataMap map[string]*QuantData `json:"-"` - OIRankingData *provider.OIRankingData `json:"-"` // Market-wide OI ranking data - BTCETHLeverage int `json:"-"` - AltcoinLeverage int `json:"-"` - Timeframes []string `json:"-"` + CurrentTime string `json:"current_time"` + CurrentTimestampMs int64 `json:"-"` // Current timestamp in milliseconds (for backtest time calculations) + RuntimeMinutes int `json:"runtime_minutes"` + CallCount int `json:"call_count"` + Account AccountInfo `json:"account"` + Positions []PositionInfo `json:"positions"` + CandidateCoins []CandidateCoin `json:"candidate_coins"` + PromptVariant string `json:"prompt_variant,omitempty"` + TradingStats *TradingStats `json:"trading_stats,omitempty"` + RecentOrders []RecentOrder `json:"recent_orders,omitempty"` + MarketDataMap map[string]*market.Data `json:"-"` + MultiTFMarket map[string]map[string]*market.Data `json:"-"` + OITopDataMap map[string]*OITopData `json:"-"` + QuantDataMap map[string]*QuantData `json:"-"` + OIRankingData *provider.OIRankingData `json:"-"` // Market-wide OI ranking data + BTCETHLeverage int `json:"-"` + AltcoinLeverage int `json:"-"` + Timeframes []string `json:"-"` } // Decision AI trading decision @@ -1035,7 +1036,12 @@ func (e *StrategyEngine) formatPositionInfo(index int, pos PositionInfo, ctx *Co holdingDuration := "" if pos.UpdateTime > 0 { - durationMs := time.Now().UnixMilli() - pos.UpdateTime + // Use context timestamp if available (for backtest), otherwise use system time (for live trading) + currentTimeMs := ctx.CurrentTimestampMs + if currentTimeMs == 0 { + currentTimeMs = time.Now().UnixMilli() + } + durationMs := currentTimeMs - pos.UpdateTime durationMin := durationMs / (1000 * 60) if durationMin < 60 { holdingDuration = fmt.Sprintf(" | Holding Duration %d min", durationMin)