refactor: simplify config and remove unused database tables

- Remove system_config, beta_codes, signal_source tables and related code
- Simplify config.go to only read from .env (APIServerPort, JWTSecret, RegistrationEnabled)
- Remove GetCustomCoins, use all USDT perpetual contracts for WSMonitor
- Add trader_equity_snapshots table for equity tracking
- Remove signal source modal from frontend AITradersPage
- Fix WSMonitor nil panic by restoring initialization in main.go
This commit is contained in:
tinkle-community
2025-12-07 20:17:03 +08:00
parent 07ac8e4ecd
commit 2334d78e4a
15 changed files with 490 additions and 1493 deletions
+25 -191
View File
@@ -76,10 +76,11 @@ type Statistics struct {
TotalClosePositions int `json:"total_close_positions"`
}
// initTables 初始化决策相关
// initTables 初始化 AI 决策日志
// 注意:账户净值曲线数据已迁移到 trader_equity_snapshots 表(由 EquityStore 管理)
func (s *DecisionStore) initTables() error {
queries := []string{
// 决策记录主表
// AI 决策日志表(记录 AI 的输入输出、思维链等)
`CREATE TABLE IF NOT EXISTS decision_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
trader_id TEXT NOT NULL,
@@ -96,58 +97,9 @@ func (s *DecisionStore) initTables() error {
ai_request_duration_ms INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// 账户状态快照表
`CREATE TABLE IF NOT EXISTS decision_account_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
decision_id INTEGER NOT NULL,
total_balance REAL DEFAULT 0,
available_balance REAL DEFAULT 0,
total_unrealized_profit REAL DEFAULT 0,
position_count INTEGER DEFAULT 0,
margin_used_pct REAL DEFAULT 0,
initial_balance REAL DEFAULT 0,
FOREIGN KEY (decision_id) REFERENCES decision_records(id) ON DELETE CASCADE
)`,
// 持仓快照表
`CREATE TABLE IF NOT EXISTS decision_position_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
decision_id INTEGER NOT NULL,
symbol TEXT NOT NULL,
side TEXT DEFAULT '',
position_amt REAL DEFAULT 0,
entry_price REAL DEFAULT 0,
mark_price REAL DEFAULT 0,
unrealized_profit REAL DEFAULT 0,
leverage REAL DEFAULT 0,
liquidation_price REAL DEFAULT 0,
FOREIGN KEY (decision_id) REFERENCES decision_records(id) ON DELETE CASCADE
)`,
// 决策动作表(订单详情)
`CREATE TABLE IF NOT EXISTS decision_actions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
decision_id INTEGER NOT NULL,
trader_id TEXT NOT NULL,
action TEXT NOT NULL,
symbol TEXT NOT NULL,
quantity REAL DEFAULT 0,
leverage INTEGER DEFAULT 0,
price REAL DEFAULT 0,
order_id INTEGER DEFAULT 0,
timestamp DATETIME NOT NULL,
success BOOLEAN DEFAULT 0,
error TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (decision_id) REFERENCES decision_records(id) ON DELETE CASCADE
)`,
// 索引
`CREATE INDEX IF NOT EXISTS idx_decision_records_trader_time ON decision_records(trader_id, timestamp DESC)`,
`CREATE INDEX IF NOT EXISTS idx_decision_records_timestamp ON decision_records(timestamp DESC)`,
`CREATE INDEX IF NOT EXISTS idx_decision_actions_trader ON decision_actions(trader_id, timestamp DESC)`,
`CREATE INDEX IF NOT EXISTS idx_decision_actions_symbol ON decision_actions(symbol, timestamp DESC)`,
}
for _, query := range queries {
@@ -159,7 +111,7 @@ func (s *DecisionStore) initTables() error {
return nil
}
// LogDecision 记录决策
// LogDecision 记录决策(仅保存 AI 决策日志,净值曲线已迁移到 equity 表)
func (s *DecisionStore) LogDecision(record *DecisionRecord) error {
if record.Timestamp.IsZero() {
record.Timestamp = time.Now().UTC()
@@ -167,19 +119,12 @@ func (s *DecisionStore) LogDecision(record *DecisionRecord) error {
record.Timestamp = record.Timestamp.UTC()
}
// 开始事务
tx, err := s.db.Begin()
if err != nil {
return fmt.Errorf("开始事务失败: %w", err)
}
defer tx.Rollback()
// 序列化候选币种和执行日志为 JSON
candidateCoinsJSON, _ := json.Marshal(record.CandidateCoins)
executionLogJSON, _ := json.Marshal(record.ExecutionLog)
// 插入决策记录主表
result, err := tx.Exec(`
// 插入决策记录主表(仅保存 AI 决策相关内容)
result, err := s.db.Exec(`
INSERT INTO decision_records (
trader_id, cycle_number, timestamp, system_prompt, input_prompt,
cot_trace, decision_json, candidate_coins, execution_log,
@@ -201,63 +146,6 @@ func (s *DecisionStore) LogDecision(record *DecisionRecord) error {
}
record.ID = decisionID
// 插入账户状态快照
_, err = tx.Exec(`
INSERT INTO decision_account_snapshots (
decision_id, total_balance, available_balance, total_unrealized_profit,
position_count, margin_used_pct, initial_balance
) VALUES (?, ?, ?, ?, ?, ?, ?)
`,
decisionID, record.AccountState.TotalBalance, record.AccountState.AvailableBalance,
record.AccountState.TotalUnrealizedProfit, record.AccountState.PositionCount,
record.AccountState.MarginUsedPct, record.AccountState.InitialBalance,
)
if err != nil {
return fmt.Errorf("插入账户快照失败: %w", err)
}
// 插入持仓快照
for _, pos := range record.Positions {
_, err = tx.Exec(`
INSERT INTO decision_position_snapshots (
decision_id, symbol, side, position_amt, entry_price,
mark_price, unrealized_profit, leverage, liquidation_price
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
decisionID, pos.Symbol, pos.Side, pos.PositionAmt, pos.EntryPrice,
pos.MarkPrice, pos.UnrealizedProfit, pos.Leverage, pos.LiquidationPrice,
)
if err != nil {
return fmt.Errorf("插入持仓快照失败: %w", err)
}
}
// 插入决策动作(订单详情)
for _, action := range record.Decisions {
actionTimestamp := action.Timestamp
if actionTimestamp.IsZero() {
actionTimestamp = record.Timestamp
}
_, err = tx.Exec(`
INSERT INTO decision_actions (
decision_id, trader_id, action, symbol, quantity, leverage,
price, order_id, timestamp, success, error
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
decisionID, record.TraderID, action.Action, action.Symbol, action.Quantity,
action.Leverage, action.Price, action.OrderID,
actionTimestamp.Format(time.RFC3339), action.Success, action.Error,
)
if err != nil {
return fmt.Errorf("插入决策动作失败: %w", err)
}
}
// 提交事务
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交事务失败: %w", err)
}
return nil
}
@@ -394,21 +282,17 @@ func (s *DecisionStore) GetStatistics(traderID string) (*Statistics, error) {
}
stats.FailedCycles = stats.TotalCycles - stats.SuccessfulCycles
err = s.db.QueryRow(`
SELECT COUNT(*) FROM decision_actions
WHERE trader_id = ? AND success = 1 AND action IN ('open_long', 'open_short')
// 从 trader_orders 表统计开仓次数
s.db.QueryRow(`
SELECT COUNT(*) FROM trader_orders
WHERE trader_id = ? AND status = 'FILLED' AND action IN ('open_long', 'open_short')
`, traderID).Scan(&stats.TotalOpenPositions)
if err != nil {
return nil, fmt.Errorf("查询开仓次数失败: %w", err)
}
err = s.db.QueryRow(`
SELECT COUNT(*) FROM decision_actions
WHERE trader_id = ? AND success = 1 AND action IN ('close_long', 'close_short', 'auto_close_long', 'auto_close_short')
// 从 trader_orders 表统计平仓次数
s.db.QueryRow(`
SELECT COUNT(*) FROM trader_orders
WHERE trader_id = ? AND status = 'FILLED' AND action IN ('close_long', 'close_short', 'auto_close_long', 'auto_close_short')
`, traderID).Scan(&stats.TotalClosePositions)
if err != nil {
return nil, fmt.Errorf("查询平仓次数失败: %w", err)
}
return stats, nil
}
@@ -421,14 +305,15 @@ func (s *DecisionStore) GetAllStatistics() (*Statistics, error) {
s.db.QueryRow(`SELECT COUNT(*) FROM decision_records WHERE success = 1`).Scan(&stats.SuccessfulCycles)
stats.FailedCycles = stats.TotalCycles - stats.SuccessfulCycles
// 从 trader_orders 表统计
s.db.QueryRow(`
SELECT COUNT(*) FROM decision_actions
WHERE success = 1 AND action IN ('open_long', 'open_short')
SELECT COUNT(*) FROM trader_orders
WHERE status = 'FILLED' AND action IN ('open_long', 'open_short')
`).Scan(&stats.TotalOpenPositions)
s.db.QueryRow(`
SELECT COUNT(*) FROM decision_actions
WHERE success = 1 AND action IN ('close_long', 'close_short', 'auto_close_long', 'auto_close_short')
SELECT COUNT(*) FROM trader_orders
WHERE status = 'FILLED' AND action IN ('close_long', 'close_short', 'auto_close_long', 'auto_close_short')
`).Scan(&stats.TotalClosePositions)
return stats, nil
@@ -469,62 +354,11 @@ func (s *DecisionStore) scanDecisionRecord(rows *sql.Rows) (*DecisionRecord, err
return &record, nil
}
// fillRecordDetails 填充决策记录的关联数据
// fillRecordDetails 填充决策记录的关联数据(旧的关联表已删除,此函数保留用于兼容性)
// 注意:账户快照、持仓快照、决策动作等数据已不再存储在 decision 相关表中
// - 净值数据请使用 EquityStore.GetLatest()
// - 订单数据请使用 OrderStore
func (s *DecisionStore) fillRecordDetails(record *DecisionRecord) {
// 查询账户状态
s.db.QueryRow(`
SELECT total_balance, available_balance, total_unrealized_profit,
position_count, margin_used_pct, initial_balance
FROM decision_account_snapshots
WHERE decision_id = ?
`, record.ID).Scan(
&record.AccountState.TotalBalance,
&record.AccountState.AvailableBalance,
&record.AccountState.TotalUnrealizedProfit,
&record.AccountState.PositionCount,
&record.AccountState.MarginUsedPct,
&record.AccountState.InitialBalance,
)
// 查询持仓快照
posRows, err := s.db.Query(`
SELECT symbol, side, position_amt, entry_price, mark_price,
unrealized_profit, leverage, liquidation_price
FROM decision_position_snapshots
WHERE decision_id = ?
`, record.ID)
if err == nil {
defer posRows.Close()
for posRows.Next() {
var pos PositionSnapshot
posRows.Scan(
&pos.Symbol, &pos.Side, &pos.PositionAmt, &pos.EntryPrice,
&pos.MarkPrice, &pos.UnrealizedProfit, &pos.Leverage,
&pos.LiquidationPrice,
)
record.Positions = append(record.Positions, pos)
}
}
// 查询决策动作
actionRows, err := s.db.Query(`
SELECT action, symbol, quantity, leverage, price, order_id,
timestamp, success, error
FROM decision_actions
WHERE decision_id = ?
`, record.ID)
if err == nil {
defer actionRows.Close()
for actionRows.Next() {
var action DecisionAction
var timestampStr string
actionRows.Scan(
&action.Action, &action.Symbol, &action.Quantity,
&action.Leverage, &action.Price, &action.OrderID,
&timestampStr, &action.Success, &action.Error,
)
action.Timestamp, _ = time.Parse(time.RFC3339, timestampStr)
record.Decisions = append(record.Decisions, action)
}
}
// 旧的关联表已删除,不再需要填充
// AccountState, Positions, Decisions 字段将保持为零值
}