mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 00:07:01 +08:00
Split strategy config by strategy type
This commit is contained in:
@@ -605,6 +605,8 @@ Rules:
|
||||
- For strategy_management:create: when the user asks you to design/recommend settings, think as the strategy designer, produce a concrete recommended config in your reply, and also put the same structured config into extracted_data.config_patch. Do not ask the user to fill fields you can reasonably choose for them.
|
||||
- For strategy_management:create: once the structured config is sufficient to create, ask for one final confirmation and set extracted_data.awaiting_final_confirmation=true. Do not execute create in that same turn.
|
||||
- For strategy_management:create: choose execute_skill only when awaiting_final_confirmation is already true and the current user message confirms the final summary. If the user changes a number, update config_patch and ask for final confirmation again.
|
||||
- For strategy_management:create: if the previous assistant reply said the strategy was not actually created yet and that the next step is to call the structured create tool, then a user request to continue/proceed means execute the current skill when the structured config is ready. Do not answer with another promise such as "I will create it now"; choose execute_skill.
|
||||
- For any mutating task, a reply that only promises future execution ("now I will create/update/start it", "result soon") is not a valid finish_task or ask_user outcome. If execution is the next step, choose execute_skill.
|
||||
- Never choose finish_task for an unfinished mutating active task by claiming it was created/updated/deleted/started/stopped. Only a real skill/tool execution outcome can support that claim.
|
||||
- If the user says they do not understand the current form, choices, or required information, choose "ask_user" and explain the current pending question in plain language before asking the next easiest question. Cover the relevant concepts from the previous assistant reply; do not collapse the answer to only the first missing field.
|
||||
- For beginner/confusion replies, give a safe recommended path when the domain supports one, but do not execute or create anything unless the user confirms after the explanation.
|
||||
@@ -616,7 +618,7 @@ Rules:
|
||||
- For trader bindings, exchange/model/strategy must resolve to an ID from Relevant disclosed resources before execution. Never invent a resource name or use a generic venue type like Binance/OKX as the bound exchange unless it appears as an actual disclosed resource.
|
||||
- For strategy_management:create, do not ask for exchange accounts or model bindings. Strategy templates are independent drafts/configs; exchange/model are only needed when creating, deploying, or starting a trader.
|
||||
- Strategy templates should be visible in the strategy list/page after creation. Do not bring up trader/model/exchange binding unless the user asks to run or deploy.
|
||||
- For strategy_management:create or strategy_management:update_config, when the user describes strategy intent, output config_patch as a partial StrategyConfig JSON object instead of leaving the default template unchanged. Example: "BTC趋势做空" should set coin_source to static BTCUSDT and add prompt/risk/entry rules for BTC trend-following short bias.
|
||||
- For strategy_management:create or strategy_management:update_config, when the user describes strategy intent, output config_patch as a partial StrategyConfig JSON object instead of leaving the default template unchanged. The product schema is type-isolated: grid strategies use only top-level strategy_type + grid_config + publish_config; AI strategies use only top-level strategy_type + ai_config + publish_config. For example, "BTC趋势做空" as an AI strategy should set ai_config.coin_source to static BTCUSDT and add ai_config prompt/risk/entry rules.
|
||||
- If there are multiple targets and the user did not disambiguate, ask a natural question with the available names.
|
||||
- If the current user message answers a missing field directly, extract it and continue.
|
||||
- extracted_data must use only canonical keys from Allowed field spec JSON. Never output aliases, translated labels, or raw user wording as keys.
|
||||
|
||||
@@ -645,12 +645,6 @@ func TestDescribeStrategyIncludesManualPageSections(t *testing.T) {
|
||||
EnableDirectionAdjust: true,
|
||||
DirectionBiasRatio: 0.7,
|
||||
}
|
||||
cfg.CoinSource.SourceType = "mixed"
|
||||
cfg.CoinSource.StaticCoins = []string{"BTCUSDT", "ETHUSDT"}
|
||||
cfg.CoinSource.ExcludedCoins = []string{"DOGEUSDT"}
|
||||
cfg.Indicators.EnableOIRanking = true
|
||||
cfg.Indicators.EnableNetFlowRanking = true
|
||||
cfg.Indicators.EnablePriceRanking = true
|
||||
rawCfg, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal strategy config: %v", err)
|
||||
@@ -682,11 +676,17 @@ func TestDescribeStrategyIncludesManualPageSections(t *testing.T) {
|
||||
"发布设置:已发布到市场;配置隐藏",
|
||||
"网格参数:交易对 BTCUSDT;网格 12;总投资 1500.00;杠杆 4;分布 gaussian",
|
||||
"网格边界:上沿 120000.0000,下沿 90000.0000",
|
||||
"标的来源:mixed | AI500=3 | static=BTCUSDT,ETHUSDT | excluded=DOGEUSDT",
|
||||
"NofxOS 数据:API Key=true,量化数据=true,OI 排行=true,净流入排行=true,价格排行=true",
|
||||
} {
|
||||
if !strings.Contains(detail, expected) {
|
||||
t.Fatalf("expected strategy detail to contain %q, got: %s", expected, detail)
|
||||
}
|
||||
}
|
||||
for _, unexpected := range []string{
|
||||
"标的来源:",
|
||||
"NofxOS 数据:",
|
||||
} {
|
||||
if strings.Contains(detail, unexpected) {
|
||||
t.Fatalf("expected grid strategy detail not to contain AI field %q, got: %s", unexpected, detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,11 +255,11 @@ func allowedFieldSpecsForSkillSession(session skillSession, lang string) []llmFl
|
||||
configPatchDescription := "Partial StrategyConfig JSON patch inferred from the user's strategy intent."
|
||||
switch explicitStrategyCreateType(session) {
|
||||
case "grid_trading":
|
||||
configPatchDescription += " Current strategy_type is grid_trading: use only grid_config and publish/common fields; do not use coin source, indicators, timeframes, confidence, or prompt-section fields."
|
||||
configPatchDescription += " Current strategy_type is grid_trading: use only top-level strategy_type, grid_config, publish_config, and language. Do not output ai_config or AI fields such as coin_source, indicators, risk_control, timeframes, confidence, or prompt_sections."
|
||||
case "ai_trading":
|
||||
configPatchDescription += " Current strategy_type is ai_trading: use coin source, indicators, risk, timeframes, and prompt sections; do not use grid_config fields."
|
||||
configPatchDescription += " Current strategy_type is ai_trading: use top-level strategy_type, ai_config, publish_config, and language. Put coin_source, indicators, risk_control, prompt_sections, and custom_prompt inside ai_config. Do not output grid_config."
|
||||
default:
|
||||
configPatchDescription += " Include strategy_type first when the user chooses AI or grid; after strategy_type is known, use only fields for that type."
|
||||
configPatchDescription += " Include strategy_type first when the user chooses AI or grid; after strategy_type is known, use only the config branch for that type: grid_config for grid, ai_config for AI."
|
||||
}
|
||||
add(&out, "config_patch", configPatchDescription, false)
|
||||
}
|
||||
|
||||
169
agent/tools.go
169
agent/tools.go
@@ -271,92 +271,99 @@ func strategyConfigSchema() map[string]any {
|
||||
"type": "object",
|
||||
"description": "Full or partial strategy config. Only include the fields you want to create or update.",
|
||||
"properties": map[string]any{
|
||||
"strategy_type": map[string]any{"type": "string", "enum": []string{"ai_trading", "grid_trading"}, "description": "ai_trading uses coin source, indicators, risk_control, and prompts. grid_trading uses grid_config and publish settings."},
|
||||
"strategy_type": map[string]any{"type": "string", "enum": []string{"ai_trading", "grid_trading"}, "description": "Top-level discriminator. ai_trading must use ai_config only. grid_trading must use grid_config only."},
|
||||
"language": map[string]any{"type": "string", "enum": []string{"zh", "en"}},
|
||||
"coin_source": map[string]any{
|
||||
"type": "object",
|
||||
"ai_config": map[string]any{
|
||||
"type": "object",
|
||||
"description": "AI trading only. Do not include this for grid_trading.",
|
||||
"properties": map[string]any{
|
||||
"source_type": map[string]any{"type": "string", "enum": []string{"static", "ai500", "oi_top", "oi_low", "mixed"}, "description": "Manual page coin source: static, ai500, oi_top, oi_low; mixed can be displayed when already configured."},
|
||||
"static_coins": stringArraySchema("Static coin symbols such as BTCUSDT or ETHUSDT. Manual page allows at most 10. xyz: assets such as xyz:TSLA, xyz:GOLD, xyz:XYZ100 are also supported."),
|
||||
"excluded_coins": stringArraySchema("Coin symbols to exclude from all sources."),
|
||||
"use_ai500": map[string]any{"type": "boolean"},
|
||||
"ai500_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_oi_top": map[string]any{"type": "boolean"},
|
||||
"oi_top_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_oi_low": map[string]any{"type": "boolean"},
|
||||
"oi_low_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_hyper_all": map[string]any{"type": "boolean"},
|
||||
"use_hyper_main": map[string]any{"type": "boolean"},
|
||||
"hyper_main_limit": map[string]any{"type": "number"},
|
||||
},
|
||||
},
|
||||
"indicators": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"klines": map[string]any{
|
||||
"coin_source": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"primary_timeframe": map[string]any{"type": "string", "enum": []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w"}},
|
||||
"primary_count": map[string]any{"type": "number", "minimum": 10, "maximum": 30, "description": "Manual page range 10-30."},
|
||||
"longer_timeframe": map[string]any{"type": "string"},
|
||||
"longer_count": map[string]any{"type": "number"},
|
||||
"enable_multi_timeframe": map[string]any{"type": "boolean"},
|
||||
"selected_timeframes": stringArraySchema("Selected analysis timeframes. Allowed values: 1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w. Manual page allows at most 4."),
|
||||
"source_type": map[string]any{"type": "string", "enum": []string{"static", "ai500", "oi_top", "oi_low", "mixed"}, "description": "Manual page coin source: static, ai500, oi_top, oi_low; mixed can be displayed when already configured."},
|
||||
"static_coins": stringArraySchema("Static coin symbols such as BTCUSDT or ETHUSDT. Manual page allows at most 10. xyz: assets such as xyz:TSLA, xyz:GOLD, xyz:XYZ100 are also supported."),
|
||||
"excluded_coins": stringArraySchema("Coin symbols to exclude from all sources."),
|
||||
"use_ai500": map[string]any{"type": "boolean"},
|
||||
"ai500_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_oi_top": map[string]any{"type": "boolean"},
|
||||
"oi_top_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_oi_low": map[string]any{"type": "boolean"},
|
||||
"oi_low_limit": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10."},
|
||||
"use_hyper_all": map[string]any{"type": "boolean"},
|
||||
"use_hyper_main": map[string]any{"type": "boolean"},
|
||||
"hyper_main_limit": map[string]any{"type": "number"},
|
||||
},
|
||||
},
|
||||
"indicators": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"klines": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"primary_timeframe": map[string]any{"type": "string", "enum": []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w"}},
|
||||
"primary_count": map[string]any{"type": "number", "minimum": 10, "maximum": 30, "description": "Manual page range 10-30."},
|
||||
"longer_timeframe": map[string]any{"type": "string"},
|
||||
"longer_count": map[string]any{"type": "number"},
|
||||
"enable_multi_timeframe": map[string]any{"type": "boolean"},
|
||||
"selected_timeframes": stringArraySchema("Selected analysis timeframes. Allowed values: 1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w. Manual page allows at most 4."),
|
||||
},
|
||||
},
|
||||
"enable_raw_klines": map[string]any{"type": "boolean"},
|
||||
"enable_ema": map[string]any{"type": "boolean"},
|
||||
"enable_macd": map[string]any{"type": "boolean"},
|
||||
"enable_rsi": map[string]any{"type": "boolean"},
|
||||
"enable_atr": map[string]any{"type": "boolean"},
|
||||
"enable_boll": map[string]any{"type": "boolean"},
|
||||
"enable_volume": map[string]any{"type": "boolean"},
|
||||
"enable_oi": map[string]any{"type": "boolean"},
|
||||
"enable_funding_rate": map[string]any{"type": "boolean"},
|
||||
"ema_periods": intArraySchema("EMA periods such as [20,50]."),
|
||||
"rsi_periods": intArraySchema("RSI periods such as [7,14]."),
|
||||
"atr_periods": intArraySchema("ATR periods such as [14]."),
|
||||
"boll_periods": intArraySchema("BOLL periods such as [20]."),
|
||||
"nofxos_api_key": map[string]any{"type": "string"},
|
||||
"enable_quant_data": map[string]any{"type": "boolean"},
|
||||
"enable_quant_oi": map[string]any{"type": "boolean"},
|
||||
"enable_quant_netflow": map[string]any{"type": "boolean"},
|
||||
"enable_oi_ranking": map[string]any{"type": "boolean"},
|
||||
"oi_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h"}},
|
||||
"oi_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
"enable_netflow_ranking": map[string]any{"type": "boolean"},
|
||||
"netflow_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h"}},
|
||||
"netflow_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
"enable_price_ranking": map[string]any{"type": "boolean"},
|
||||
"price_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h", "1h,4h,24h"}},
|
||||
"price_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
},
|
||||
},
|
||||
"custom_prompt": map[string]any{"type": "string"},
|
||||
"risk_control": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"max_positions": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless the user explicitly asks for advanced configuration."},
|
||||
"btc_eth_max_leverage": map[string]any{"type": "number", "minimum": 1, "maximum": 20},
|
||||
"altcoin_max_leverage": map[string]any{"type": "number", "minimum": 1, "maximum": 20},
|
||||
"btc_eth_max_position_value_ratio": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"altcoin_max_position_value_ratio": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"max_margin_usage": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"min_risk_reward_ratio": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10, step 0.5."},
|
||||
"min_confidence": map[string]any{"type": "number", "minimum": 50, "maximum": 100, "description": "Manual page range 50-100."},
|
||||
},
|
||||
},
|
||||
"prompt_sections": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"role_definition": map[string]any{"type": "string"},
|
||||
"trading_frequency": map[string]any{"type": "string"},
|
||||
"entry_standards": map[string]any{"type": "string"},
|
||||
"decision_process": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"enable_raw_klines": map[string]any{"type": "boolean"},
|
||||
"enable_ema": map[string]any{"type": "boolean"},
|
||||
"enable_macd": map[string]any{"type": "boolean"},
|
||||
"enable_rsi": map[string]any{"type": "boolean"},
|
||||
"enable_atr": map[string]any{"type": "boolean"},
|
||||
"enable_boll": map[string]any{"type": "boolean"},
|
||||
"enable_volume": map[string]any{"type": "boolean"},
|
||||
"enable_oi": map[string]any{"type": "boolean"},
|
||||
"enable_funding_rate": map[string]any{"type": "boolean"},
|
||||
"ema_periods": intArraySchema("EMA periods such as [20,50]."),
|
||||
"rsi_periods": intArraySchema("RSI periods such as [7,14]."),
|
||||
"atr_periods": intArraySchema("ATR periods such as [14]."),
|
||||
"boll_periods": intArraySchema("BOLL periods such as [20]."),
|
||||
"nofxos_api_key": map[string]any{"type": "string"},
|
||||
"enable_quant_data": map[string]any{"type": "boolean"},
|
||||
"enable_quant_oi": map[string]any{"type": "boolean"},
|
||||
"enable_quant_netflow": map[string]any{"type": "boolean"},
|
||||
"enable_oi_ranking": map[string]any{"type": "boolean"},
|
||||
"oi_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h"}},
|
||||
"oi_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
"enable_netflow_ranking": map[string]any{"type": "boolean"},
|
||||
"netflow_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h"}},
|
||||
"netflow_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
"enable_price_ranking": map[string]any{"type": "boolean"},
|
||||
"price_ranking_duration": map[string]any{"type": "string", "enum": []string{"1h", "4h", "24h", "1h,4h,24h"}},
|
||||
"price_ranking_limit": map[string]any{"type": "number", "enum": []int{5, 10, 15, 20}},
|
||||
},
|
||||
},
|
||||
"custom_prompt": map[string]any{"type": "string"},
|
||||
"risk_control": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"max_positions": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless the user explicitly asks for advanced configuration."},
|
||||
"btc_eth_max_leverage": map[string]any{"type": "number", "minimum": 1, "maximum": 20},
|
||||
"altcoin_max_leverage": map[string]any{"type": "number", "minimum": 1, "maximum": 20},
|
||||
"btc_eth_max_position_value_ratio": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"altcoin_max_position_value_ratio": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"max_margin_usage": map[string]any{"type": "number", "description": "Displayed as System enforced on the manual strategy page; do not change unless explicitly requested."},
|
||||
"min_risk_reward_ratio": map[string]any{"type": "number", "minimum": 1, "maximum": 10, "description": "Manual page range 1-10, step 0.5."},
|
||||
"min_confidence": map[string]any{"type": "number", "minimum": 50, "maximum": 100, "description": "Manual page range 50-100."},
|
||||
},
|
||||
},
|
||||
"prompt_sections": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"role_definition": map[string]any{"type": "string"},
|
||||
"trading_frequency": map[string]any{"type": "string"},
|
||||
"entry_standards": map[string]any{"type": "string"},
|
||||
"decision_process": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
"grid_config": map[string]any{
|
||||
"type": "object",
|
||||
"description": "Grid trading only. Do not include this for ai_trading.",
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"symbol": map[string]any{"type": "string", "enum": []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT"}, "description": "Manual page dropdown options for grid trading symbols."},
|
||||
"grid_count": map[string]any{"type": "number", "minimum": 5, "maximum": 50, "description": "Manual page range 5-50."},
|
||||
@@ -375,6 +382,14 @@ func strategyConfigSchema() map[string]any {
|
||||
"direction_bias_ratio": map[string]any{"type": "number", "minimum": 0.55, "maximum": 0.9, "description": "Manual page range 0.55-0.90 (shown as 55%-90%)."},
|
||||
},
|
||||
},
|
||||
"publish_config": map[string]any{
|
||||
"type": "object",
|
||||
"description": "Shared publish settings for both AI and grid strategies.",
|
||||
"properties": map[string]any{
|
||||
"is_public": map[string]any{"type": "boolean"},
|
||||
"config_visible": map[string]any{"type": "boolean"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user