Skip to content

Commit 8834c88

Browse files
the-dev-zclaude
andcommitted
fix: 动态调整最小开仓金额以支持小账户
## 问题 - 小账户(<20 USDT)无法交易 BTC/ETH,因固定最小金额(60 USDT)超过账户规模 - 用户报告: 净值 6.97 USDT,AI 建议开仓 30 USDT → 验证失败 - 造成"鸡生蛋问题": 账户太小无法参与主流币种交易 ## 解决方案 实施动态最小金额计算策略: - 小账户(<20U): BTC/ETH 最小 12 USDT (与山寨币相同) - 中型账户(20-100U): 线性插值 12-60 USDT (平滑过渡) - 大账户(≥100U): 标准 60 USDT (保持原策略) - 山寨币: 固定 12 USDT (无变化) ## 变更内容 1. **新增函数** `calculateMinPositionSize()`: - 根据账户净值和币种动态计算最小开仓金额 - 例: 6.97U账户 → BTC/ETH最小12U, 60U账户 → 36U 2. **更新验证逻辑** (`validateDecision`): - 替换固定常量为动态计算 - 小账户特殊错误提示,引导用户理解动态门槛 3. **更新 AI Prompt** (`buildSystemPrompt`): - 根据账户规模动态提示最小金额 - 小账户: "BTC/ETH≥12 USDT (⚠️ 小账户模式)" - 中型账户: "BTC/ETH≥XX USDT (根据账户规模动态调整)" ## 测试 - ✅ 所有单元测试通过 (go test ./decision/... -v) - ✅ 代码格式化 (go fmt) - ✅ 无破坏性变更,向后兼容 ## 影响范围 - 仅影响开仓验证逻辑,不影响现有持仓 - 降低小账户交易门槛,提升用户体验 - 保持大账户原有安全边际 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ad3d6b9 commit 8834c88

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

decision/engine.go

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,21 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
331331
accountEquity*0.8, accountEquity*1.5, accountEquity*5, accountEquity*10))
332332
sb.WriteString(fmt.Sprintf("4. 杠杆限制: **山寨币最大%dx杠杆** | **BTC/ETH最大%dx杠杆** (⚠️ 严格执行,不可超过)\n", altcoinLeverage, btcEthLeverage))
333333
sb.WriteString("5. 保证金: 总使用率 ≤ 90%\n")
334-
sb.WriteString("6. 开仓金额: 建议 **≥12 USDT** (交易所最小名义价值 10 USDT + 安全边际)\n\n")
334+
335+
// 6. 开仓金额:根据账户规模动态提示
336+
sb.WriteString("6. 开仓金额: 山寨币≥12 USDT")
337+
if accountEquity < 20.0 {
338+
// 小账户特殊提示
339+
sb.WriteString(" | BTC/ETH≥12 USDT (⚠️ 小账户模式,降低门槛)")
340+
} else if accountEquity < 100.0 {
341+
// 中型账户动态门槛
342+
minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)
343+
sb.WriteString(fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH))
344+
} else {
345+
// 大账户标准门槛
346+
sb.WriteString(" | BTC/ETH≥60 USDT")
347+
}
348+
sb.WriteString("\n\n")
335349

336350
// 3. 输出格式 - 动态生成
337351
sb.WriteString("# 输出格式 (严格遵守)\n\n")
@@ -701,6 +715,36 @@ func findMatchingBracket(s string, start int) int {
701715
return -1
702716
}
703717

718+
// calculateMinPositionSize 根据账户净值和币种动态计算最小开仓金额
719+
func calculateMinPositionSize(symbol string, accountEquity float64) float64 {
720+
const (
721+
absoluteMinimum = 12.0 // 交易所绝对最小值 (10 USDT + 20% 安全边际)
722+
standardBTCETH = 60.0 // 标准 BTC/ETH 最小值 (因价格高和精度限制)
723+
)
724+
725+
isBTCETH := symbol == "BTCUSDT" || symbol == "ETHUSDT"
726+
727+
// 山寨币始终使用绝对最小值
728+
if !isBTCETH {
729+
return absoluteMinimum
730+
}
731+
732+
// BTC/ETH 动态调整策略
733+
// 小账户(<20U): 使用绝对最小值,避免完全无法交易
734+
if accountEquity < 20.0 {
735+
return absoluteMinimum
736+
}
737+
738+
// 中型账户(20-100U): 线性插值,平滑过渡
739+
// 例: 20U账户→12U, 60U账户→36U, 100U账户→60U
740+
if accountEquity < 100.0 {
741+
return absoluteMinimum + (standardBTCETH-absoluteMinimum)*(accountEquity-20.0)/80.0
742+
}
743+
744+
// 大账户(≥100U): 使用标准值
745+
return standardBTCETH
746+
}
747+
704748
// validateDecision 验证单个决策的有效性
705749
func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error {
706750
// 验证action
@@ -744,18 +788,17 @@ func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoi
744788
}
745789

746790
// ✅ 验证最小开仓金额(防止数量格式化为 0 的错误)
747-
// Binance 最小名义价值 10 USDT + 安全边际
748-
const minPositionSizeGeneral = 12.0 // 10 + 20% 安全边际
749-
const minPositionSizeBTCETH = 60.0 // BTC/ETH 因价格高和精度限制需要更大金额(更灵活)
750-
751-
if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" {
752-
if d.PositionSizeUSD < minPositionSizeBTCETH {
753-
return fmt.Errorf("%s 开仓金额过小(%.2f USDT),必须≥%.2f USDT(因价格高且精度限制,避免数量四舍五入为0)", d.Symbol, d.PositionSizeUSD, minPositionSizeBTCETH)
754-
}
755-
} else {
756-
if d.PositionSizeUSD < minPositionSizeGeneral {
757-
return fmt.Errorf("开仓金额过小(%.2f USDT),必须≥%.2f USDT(Binance 最小名义价值要求)", d.PositionSizeUSD, minPositionSizeGeneral)
791+
// 使用动态计算函数,根据账户规模自适应调整
792+
minPositionSize := calculateMinPositionSize(d.Symbol, accountEquity)
793+
if d.PositionSizeUSD < minPositionSize {
794+
// 小账户特殊提示:引导用户理解动态门槛
795+
if accountEquity < 20.0 && (d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT") {
796+
return fmt.Errorf("%s 开仓金额过小(%.2f USDT),当前账户规模(%.2f USDT)要求≥%.2f USDT(小账户动态调整)",
797+
d.Symbol, d.PositionSizeUSD, accountEquity, minPositionSize)
758798
}
799+
// 通用错误提示
800+
return fmt.Errorf("开仓金额过小(%.2f USDT),必须≥%.2f USDT(交易所最小名义价值要求)",
801+
d.PositionSizeUSD, minPositionSize)
759802
}
760803

761804
// 验证仓位价值上限(加1%容差以避免浮点数精度问题)

decision/prompt_manager_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestPromptManager_LoadTemplates(t *testing.T) {
2929
{
3030
name: "加载多个模板文件",
3131
setupFiles: map[string]string{
32-
"default.txt": "默认策略",
32+
"default.txt": "默认策略",
3333
"conservative.txt": "保守策略",
3434
"aggressive.txt": "激进策略",
3535
},
@@ -130,15 +130,15 @@ func TestPromptManager_GetTemplate(t *testing.T) {
130130
}
131131

132132
tests := []struct {
133-
name string
134-
templateName string
135-
expectError bool
133+
name string
134+
templateName string
135+
expectError bool
136136
expectedContent string
137137
}{
138138
{
139-
name: "获取存在的模板",
140-
templateName: "default",
141-
expectError: false,
139+
name: "获取存在的模板",
140+
templateName: "default",
141+
expectError: false,
142142
expectedContent: "默认策略内容",
143143
},
144144
{
@@ -225,7 +225,7 @@ func TestPromptManager_ReloadTemplates(t *testing.T) {
225225
func TestPromptManager_GetAllTemplateNames(t *testing.T) {
226226
pm := NewPromptManager()
227227
pm.templates = map[string]*PromptTemplate{
228-
"default": {Name: "default", Content: "默认策略"},
228+
"default": {Name: "default", Content: "默认策略"},
229229
"conservative": {Name: "conservative", Content: "保守策略"},
230230
"aggressive": {Name: "aggressive", Content: "激进策略"},
231231
}

0 commit comments

Comments
 (0)