fix: initial balance calculation and UI improvements

- Fix initial balance using available_balance instead of total_equity
- Fix WSMonitor nil pointer by starting market monitor before loading traders
- Add strategy name display on traders list and dashboard pages
- Various position sync and trading improvements
This commit is contained in:
tinkle-community
2025-12-10 14:40:08 +08:00
parent c19ee51dee
commit 319ccb8ca3
45 changed files with 2951 additions and 3392 deletions

View File

@@ -6,6 +6,7 @@ import (
"time"
"nofx/market"
"nofx/store"
)
// AIConfig defines the AI client configuration used in backtesting.
@@ -176,3 +177,61 @@ func validateFillPolicy(policy string) error {
return fmt.Errorf("unsupported fill_policy '%s'", policy)
}
}
// ToStrategyConfig converts BacktestConfig to StrategyConfig for unified prompt generation.
// This ensures backtest uses the same StrategyEngine logic as live trading.
func (cfg *BacktestConfig) ToStrategyConfig() *store.StrategyConfig {
// Determine primary and longer timeframe from the timeframes list
primaryTF := "5m"
longerTF := "4h"
if len(cfg.Timeframes) > 0 {
primaryTF = cfg.Timeframes[0]
}
if len(cfg.Timeframes) > 1 {
longerTF = cfg.Timeframes[len(cfg.Timeframes)-1]
}
return &store.StrategyConfig{
CoinSource: store.CoinSourceConfig{
SourceType: "static",
StaticCoins: cfg.Symbols,
UseCoinPool: false,
CoinPoolLimit: len(cfg.Symbols),
UseOITop: false,
OITopLimit: 0,
},
Indicators: store.IndicatorConfig{
Klines: store.KlineConfig{
PrimaryTimeframe: primaryTF,
PrimaryCount: 30,
LongerTimeframe: longerTF,
LongerCount: 10,
EnableMultiTimeframe: len(cfg.Timeframes) > 1,
SelectedTimeframes: cfg.Timeframes,
},
EnableRawKlines: true,
EnableEMA: true,
EnableMACD: true,
EnableRSI: true,
EnableATR: true,
EnableVolume: true,
EnableOI: true,
EnableFundingRate: true,
EMAPeriods: []int{20, 50},
RSIPeriods: []int{7, 14},
ATRPeriods: []int{14},
},
CustomPrompt: cfg.CustomPrompt,
RiskControl: store.RiskControlConfig{
MaxPositions: 3,
BTCETHMaxLeverage: cfg.Leverage.BTCETHLeverage,
AltcoinMaxLeverage: cfg.Leverage.AltcoinLeverage,
BTCETHMaxPositionValueRatio: 5.0,
AltcoinMaxPositionValueRatio: 1.0,
MaxMarginUsage: 0.9,
MinPositionSize: 12,
MinRiskRewardRatio: 3.0,
MinConfidence: 75,
},
}
}

View File

@@ -31,9 +31,10 @@ const (
// Runner encapsulates the lifecycle of a single backtest run.
type Runner struct {
cfg BacktestConfig
feed *DataFeed
account *BacktestAccount
cfg BacktestConfig
feed *DataFeed
account *BacktestAccount
strategyEngine *decision.StrategyEngine
decisionLogDir string
mcpClient mcp.AIClient
@@ -115,10 +116,15 @@ func NewRunner(cfg BacktestConfig, mcpClient mcp.AIClient) (*Runner, error) {
aiCache = cache
}
// Create strategy engine from backtest config for unified prompt generation
strategyConfig := cfg.ToStrategyConfig()
strategyEngine := decision.NewStrategyEngine(strategyConfig)
r := &Runner{
cfg: cfg,
feed: feed,
account: account,
strategyEngine: strategyEngine,
decisionLogDir: dLogDir,
mcpClient: client,
status: RunStateCreated,
@@ -492,7 +498,7 @@ 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(time.RFC3339),
CurrentTime: time.UnixMilli(ts).UTC().Format("2006-01-02 15:04:05 UTC"),
RuntimeMinutes: runtime,
CallCount: callCount,
Account: accountInfo,
@@ -503,6 +509,7 @@ func (r *Runner) buildDecisionContext(ts int64, marketData map[string]*market.Da
MultiTFMarket: multiTF,
BTCETHLeverage: r.cfg.Leverage.BTCETHLeverage,
AltcoinLeverage: r.cfg.Leverage.AltcoinLeverage,
Timeframes: r.cfg.Timeframes,
}
record := &store.DecisionRecord{
@@ -537,12 +544,13 @@ func (r *Runner) fillDecisionRecord(record *store.DecisionRecord, full *decision
func (r *Runner) invokeAIWithRetry(ctx *decision.Context) (*decision.FullDecision, error) {
var lastErr error
for attempt := 0; attempt < aiDecisionMaxRetries; attempt++ {
fd, err := decision.GetFullDecisionWithCustomPrompt(
// Use GetFullDecisionWithStrategy with the pre-configured strategy engine
// This ensures backtest uses the same unified prompt generation as live trading
fd, err := decision.GetFullDecisionWithStrategy(
ctx,
r.mcpClient,
r.cfg.CustomPrompt,
r.cfg.OverrideBasePrompt,
r.cfg.PromptTemplate,
r.strategyEngine,
r.cfg.PromptVariant,
)
if err == nil {
return fd, nil