mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 16:26:57 +08:00
refactor: remove database pre-population and add i18n strategy templates
- Remove initDefaultData() for exchanges, ai_models, strategies tables - Change supported exchanges/models API to return static lists - Add GetDefaultStrategyConfig(lang) with Chinese/English prompt templates - Frontend passes language parameter when creating new strategy
This commit is contained in:
@@ -2010,43 +2010,28 @@ func (s *Server) initUserDefaultConfigs(userID string) error {
|
|||||||
|
|
||||||
// handleGetSupportedModels Get list of AI models supported by the system
|
// handleGetSupportedModels Get list of AI models supported by the system
|
||||||
func (s *Server) handleGetSupportedModels(c *gin.Context) {
|
func (s *Server) handleGetSupportedModels(c *gin.Context) {
|
||||||
// Return system-supported AI models (get from default user)
|
// Return static list of supported AI models
|
||||||
models, err := s.store.AIModel().List("default")
|
supportedModels := []map[string]interface{}{
|
||||||
if err != nil {
|
{"id": "deepseek", "name": "DeepSeek", "provider": "deepseek"},
|
||||||
logger.Infof("❌ Failed to get supported AI models: %v", err)
|
{"id": "qwen", "name": "Qwen", "provider": "qwen"},
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get supported AI models"})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models)
|
c.JSON(http.StatusOK, supportedModels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetSupportedExchanges Get list of exchanges supported by the system
|
// handleGetSupportedExchanges Get list of exchanges supported by the system
|
||||||
func (s *Server) handleGetSupportedExchanges(c *gin.Context) {
|
func (s *Server) handleGetSupportedExchanges(c *gin.Context) {
|
||||||
// Return system-supported exchanges (get from default user)
|
// Return static list of supported exchanges
|
||||||
exchanges, err := s.store.Exchange().List("default")
|
supportedExchanges := []SafeExchangeConfig{
|
||||||
if err != nil {
|
{ID: "binance", Name: "Binance Futures", Type: "binance"},
|
||||||
logger.Infof("❌ Failed to get supported exchanges: %v", err)
|
{ID: "bybit", Name: "Bybit Futures", Type: "bybit"},
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get supported exchanges"})
|
{ID: "okx", Name: "OKX Futures", Type: "okx"},
|
||||||
return
|
{ID: "hyperliquid", Name: "Hyperliquid", Type: "hyperliquid"},
|
||||||
|
{ID: "aster", Name: "Aster DEX", Type: "aster"},
|
||||||
|
{ID: "lighter", Name: "LIGHTER DEX", Type: "lighter"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to safe response structure, remove sensitive information
|
c.JSON(http.StatusOK, supportedExchanges)
|
||||||
safeExchanges := make([]SafeExchangeConfig, len(exchanges))
|
|
||||||
for i, exchange := range exchanges {
|
|
||||||
safeExchanges[i] = SafeExchangeConfig{
|
|
||||||
ID: exchange.ID,
|
|
||||||
Name: exchange.Name,
|
|
||||||
Type: exchange.Type,
|
|
||||||
Enabled: exchange.Enabled,
|
|
||||||
Testnet: exchange.Testnet,
|
|
||||||
HyperliquidWalletAddr: "", // Default config does not include wallet address
|
|
||||||
AsterUser: "", // Default config does not include user info
|
|
||||||
AsterSigner: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, safeExchanges)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Start server
|
// Start Start server
|
||||||
|
|||||||
@@ -283,75 +283,14 @@ func (s *Server) handleGetActiveStrategy(c *gin.Context) {
|
|||||||
|
|
||||||
// handleGetDefaultStrategyConfig Get default strategy configuration template
|
// handleGetDefaultStrategyConfig Get default strategy configuration template
|
||||||
func (s *Server) handleGetDefaultStrategyConfig(c *gin.Context) {
|
func (s *Server) handleGetDefaultStrategyConfig(c *gin.Context) {
|
||||||
// Return default configuration structure for frontend to use when creating new strategies
|
// Get language from query parameter, default to "en"
|
||||||
defaultConfig := store.StrategyConfig{
|
lang := c.Query("lang")
|
||||||
CoinSource: store.CoinSourceConfig{
|
if lang != "zh" {
|
||||||
SourceType: "coinpool",
|
lang = "en"
|
||||||
UseCoinPool: true,
|
|
||||||
CoinPoolLimit: 30,
|
|
||||||
UseOITop: true,
|
|
||||||
OITopLimit: 20,
|
|
||||||
StaticCoins: []string{},
|
|
||||||
},
|
|
||||||
Indicators: store.IndicatorConfig{
|
|
||||||
Klines: store.KlineConfig{
|
|
||||||
PrimaryTimeframe: "5m",
|
|
||||||
PrimaryCount: 30,
|
|
||||||
LongerTimeframe: "4h",
|
|
||||||
LongerCount: 10,
|
|
||||||
EnableMultiTimeframe: true,
|
|
||||||
SelectedTimeframes: []string{"5m", "15m", "1h", "4h"},
|
|
||||||
},
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
RiskControl: store.RiskControlConfig{
|
|
||||||
MaxPositions: 3,
|
|
||||||
BTCETHMaxLeverage: 5,
|
|
||||||
AltcoinMaxLeverage: 5,
|
|
||||||
MinRiskRewardRatio: 3.0,
|
|
||||||
MaxMarginUsage: 0.9,
|
|
||||||
MaxPositionRatio: 1.5,
|
|
||||||
MinPositionSize: 12,
|
|
||||||
MinConfidence: 75,
|
|
||||||
},
|
|
||||||
PromptSections: store.PromptSectionsConfig{
|
|
||||||
RoleDefinition: `# You are a professional cryptocurrency trading AI
|
|
||||||
|
|
||||||
You focus on technical analysis and risk management, making rational trading decisions based on market data.
|
|
||||||
Your goal is to capture high-probability trading opportunities while controlling risk.`,
|
|
||||||
TradingFrequency: `# ⏱️ Trading Frequency Awareness
|
|
||||||
|
|
||||||
- Excellent traders: 2-4 trades per day ≈ 0.1-0.2 trades per hour
|
|
||||||
- >2 trades per hour = overtrading
|
|
||||||
- Single position holding time ≥30-60 minutes
|
|
||||||
If you find yourself trading every cycle → standards too low; if closing positions <30 minutes → too impatient.`,
|
|
||||||
EntryStandards: `# 🎯 Entry Standards (Strict)
|
|
||||||
|
|
||||||
Only enter when multiple signals align:
|
|
||||||
- Clear trend direction (EMA alignment, price position)
|
|
||||||
- Momentum confirmation (MACD, RSI cooperation)
|
|
||||||
- Moderate volatility (ATR reasonable range)
|
|
||||||
- Volume-price coordination (volume supports direction)
|
|
||||||
|
|
||||||
Avoid: single indicator, conflicting signals, sideways consolidation, reopening immediately after closing.`,
|
|
||||||
DecisionProcess: `# 📋 Decision Process
|
|
||||||
|
|
||||||
1. Check positions → Should take profit/stop loss
|
|
||||||
2. Scan candidate coins + multiple timeframes → Are there strong signals
|
|
||||||
3. Evaluate risk-reward ratio → Does it meet minimum requirements
|
|
||||||
4. Write chain of thought first, then output structured JSON`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return default configuration with i18n support
|
||||||
|
defaultConfig := store.GetDefaultStrategyConfig(lang)
|
||||||
c.JSON(http.StatusOK, defaultConfig)
|
c.JSON(http.StatusOK, defaultConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,22 +69,7 @@ func (s *AIModelStore) initTables() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AIModelStore) initDefaultData() error {
|
func (s *AIModelStore) initDefaultData() error {
|
||||||
models := []struct {
|
// No longer pre-populate AI models - create on demand when user configures
|
||||||
id, name, provider string
|
|
||||||
}{
|
|
||||||
{"deepseek", "DeepSeek", "deepseek"},
|
|
||||||
{"qwen", "Qwen", "qwen"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, model := range models {
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
INSERT OR IGNORE INTO ai_models (id, user_id, name, provider, enabled)
|
|
||||||
VALUES (?, 'default', ?, ?, 0)
|
|
||||||
`, model.id, model.name, model.provider)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to initialize AI model: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,26 +80,7 @@ func (s *ExchangeStore) initTables() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ExchangeStore) initDefaultData() error {
|
func (s *ExchangeStore) initDefaultData() error {
|
||||||
exchanges := []struct {
|
// No longer pre-populate exchanges - create on demand when user configures
|
||||||
id, name, typ string
|
|
||||||
}{
|
|
||||||
{"binance", "Binance Futures", "binance"},
|
|
||||||
{"bybit", "Bybit Futures", "bybit"},
|
|
||||||
{"okx", "OKX Futures", "okx"},
|
|
||||||
{"hyperliquid", "Hyperliquid", "hyperliquid"},
|
|
||||||
{"aster", "Aster DEX", "aster"},
|
|
||||||
{"lighter", "LIGHTER DEX", "lighter"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exchange := range exchanges {
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled)
|
|
||||||
VALUES (?, 'default', ?, ?, 0)
|
|
||||||
`, exchange.id, exchange.name, exchange.typ)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to initialize exchange: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,38 +98,8 @@ func (s *ExchangeStore) decrypt(encrypted string) string {
|
|||||||
return encrypted
|
return encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureUserExchanges ensures user has records for all supported exchanges
|
|
||||||
func (s *ExchangeStore) EnsureUserExchanges(userID string) error {
|
|
||||||
exchanges := []struct {
|
|
||||||
id, name, typ string
|
|
||||||
}{
|
|
||||||
{"binance", "Binance Futures", "binance"},
|
|
||||||
{"bybit", "Bybit Futures", "bybit"},
|
|
||||||
{"okx", "OKX Futures", "okx"},
|
|
||||||
{"hyperliquid", "Hyperliquid", "hyperliquid"},
|
|
||||||
{"aster", "Aster DEX", "aster"},
|
|
||||||
{"lighter", "LIGHTER DEX", "lighter"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exchange := range exchanges {
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled)
|
|
||||||
VALUES (?, ?, ?, ?, 0)
|
|
||||||
`, exchange.id, userID, exchange.name, exchange.typ)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to ensure user exchanges: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List gets user's exchange list
|
// List gets user's exchange list
|
||||||
func (s *ExchangeStore) List(userID string) ([]*Exchange, error) {
|
func (s *ExchangeStore) List(userID string) ([]*Exchange, error) {
|
||||||
// Ensure user has records for all supported exchanges
|
|
||||||
if err := s.EnsureUserExchanges(userID); err != nil {
|
|
||||||
logger.Debugf("Warning: failed to ensure user exchange records: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := s.db.Query(`
|
rows, err := s.db.Query(`
|
||||||
SELECT id, user_id, name, type, enabled, api_key, secret_key,
|
SELECT id, user_id, name, type, enabled, api_key, secret_key,
|
||||||
COALESCE(passphrase, '') as passphrase, testnet,
|
COALESCE(passphrase, '') as passphrase, testnet,
|
||||||
|
|||||||
@@ -178,15 +178,13 @@ func (s *StrategyStore) initTables() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StrategyStore) initDefaultData() error {
|
func (s *StrategyStore) initDefaultData() error {
|
||||||
// check if default strategy already exists
|
// No longer pre-populate strategies - create on demand when user configures
|
||||||
var count int
|
return nil
|
||||||
s.db.QueryRow(`SELECT COUNT(*) FROM strategies WHERE is_default = 1`).Scan(&count)
|
}
|
||||||
if count > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create system default strategy
|
// GetDefaultStrategyConfig returns the default strategy configuration for the given language
|
||||||
defaultConfig := StrategyConfig{
|
func GetDefaultStrategyConfig(lang string) StrategyConfig {
|
||||||
|
config := StrategyConfig{
|
||||||
CoinSource: CoinSourceConfig{
|
CoinSource: CoinSourceConfig{
|
||||||
SourceType: "coinpool",
|
SourceType: "coinpool",
|
||||||
UseCoinPool: true,
|
UseCoinPool: true,
|
||||||
@@ -214,8 +212,8 @@ func (s *StrategyStore) initDefaultData() error {
|
|||||||
EMAPeriods: []int{20, 50},
|
EMAPeriods: []int{20, 50},
|
||||||
RSIPeriods: []int{7, 14},
|
RSIPeriods: []int{7, 14},
|
||||||
ATRPeriods: []int{14},
|
ATRPeriods: []int{14},
|
||||||
EnableQuantData: true,
|
EnableQuantData: true,
|
||||||
QuantDataAPIURL: "http://nofxaios.com:30006/api/coin/{symbol}?include=netflow,oi,price&auth=cm_568c67eae410d912c54c",
|
QuantDataAPIURL: "http://nofxaios.com:30006/api/coin/{symbol}?include=netflow,oi,price&auth=cm_568c67eae410d912c54c",
|
||||||
},
|
},
|
||||||
RiskControl: RiskControlConfig{
|
RiskControl: RiskControlConfig{
|
||||||
MaxPositions: 3,
|
MaxPositions: 3,
|
||||||
@@ -227,7 +225,30 @@ func (s *StrategyStore) initDefaultData() error {
|
|||||||
MinPositionSize: 12,
|
MinPositionSize: 12,
|
||||||
MinConfidence: 75,
|
MinConfidence: 75,
|
||||||
},
|
},
|
||||||
PromptSections: PromptSectionsConfig{
|
}
|
||||||
|
|
||||||
|
if lang == "zh" {
|
||||||
|
config.PromptSections = PromptSectionsConfig{
|
||||||
|
RoleDefinition: `# 你是一个专业的加密货币交易AI
|
||||||
|
|
||||||
|
你的任务是根据提供的市场数据做出交易决策。你是一个经验丰富的量化交易员,擅长技术分析和风险管理。`,
|
||||||
|
TradingFrequency: `# ⏱️ 交易频率意识
|
||||||
|
|
||||||
|
- 优秀交易员:每天2-4笔 ≈ 每小时0.1-0.2笔
|
||||||
|
- 每小时超过2笔 = 过度交易
|
||||||
|
- 单笔持仓时间 ≥ 30-60分钟
|
||||||
|
如果你发现自己每个周期都在交易 → 标准太低;如果持仓不到30分钟就平仓 → 太冲动。`,
|
||||||
|
EntryStandards: `# 🎯 入场标准(严格)
|
||||||
|
|
||||||
|
只在多个信号共振时入场。自由使用任何有效的分析方法,避免单一指标、信号矛盾、横盘震荡、或平仓后立即重新开仓等低质量行为。`,
|
||||||
|
DecisionProcess: `# 📋 决策流程
|
||||||
|
|
||||||
|
1. 检查持仓 → 是否止盈/止损
|
||||||
|
2. 扫描候选币种 + 多时间框架 → 是否存在强信号
|
||||||
|
3. 先写思维链,再输出结构化JSON`,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.PromptSections = PromptSectionsConfig{
|
||||||
RoleDefinition: `# You are a professional cryptocurrency trading AI
|
RoleDefinition: `# You are a professional cryptocurrency trading AI
|
||||||
|
|
||||||
Your task is to make trading decisions based on the provided market data. You are an experienced quantitative trader skilled in technical analysis and risk management.`,
|
Your task is to make trading decisions based on the provided market data. You are an experienced quantitative trader skilled in technical analysis and risk management.`,
|
||||||
@@ -245,17 +266,10 @@ Only enter positions when multiple signals resonate. Freely use any effective an
|
|||||||
1. Check positions → whether to take profit/stop loss
|
1. Check positions → whether to take profit/stop loss
|
||||||
2. Scan candidate coins + multi-timeframe → whether strong signals exist
|
2. Scan candidate coins + multi-timeframe → whether strong signals exist
|
||||||
3. Write chain of thought first, then output structured JSON`,
|
3. Write chain of thought first, then output structured JSON`,
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configJSON, _ := json.Marshal(defaultConfig)
|
return config
|
||||||
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
INSERT INTO strategies (id, user_id, name, description, is_active, is_default, config)
|
|
||||||
VALUES ('default', 'system', 'Default Altcoin Strategy', 'System default altcoin trading strategy, uses AI500 coin pool, includes complete technical indicators', 0, 1, ?)
|
|
||||||
`, string(configJSON))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create create a strategy
|
// Create create a strategy
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export function StrategyStudioPage() {
|
|||||||
if (!token) return
|
if (!token) return
|
||||||
try {
|
try {
|
||||||
const configResponse = await fetch(
|
const configResponse = await fetch(
|
||||||
`${API_BASE}/api/strategies/default-config`,
|
`${API_BASE}/api/strategies/default-config?lang=${language}`,
|
||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
)
|
)
|
||||||
const defaultConfig = await configResponse.json()
|
const defaultConfig = await configResponse.json()
|
||||||
|
|||||||
Reference in New Issue
Block a user