fix: update token limits and error handling in Trader Dashboard

This commit is contained in:
Dean
2026-03-28 00:04:14 +08:00
committed by shinchan-zhai
parent fbca4166a1
commit 2e2598e4e0
5 changed files with 64 additions and 23 deletions
+11 -11
View File
@@ -82,12 +82,12 @@ maxSafeCoins = floor((budget - staticTokens) / perCoinTokens)
### 各模型下的最大安全币数
| 模型上限 | 最小配置 | 默认配置 | 最大配置 |
| ------------------------------ | ----------- | --------------- | ----------- |
| 131KDeepSeek / Grok / Qwen | ≥50(封顶) | **58** | **14** |
| 128KOpenAI GPT-4 | ≥50(封顶) | **57** | **14** |
| 200KClaude | ≥50(封顶) | **89 → 封顶50** | **22** |
| 1MGemini / Minimax | ≥50(封顶) | ≥50(封顶) | ≥50(封顶) |
| 模型上限 | 最小配置 | 默认配置 | 最大配置 |
| ------------------------------ | ------------ | ------------ | ----------- |
| 131KDeepSeek / Grok / Qwen | ≥10(封顶) | ≥10(封顶) | **14** |
| 128KOpenAI GPT-4 | ≥10(封顶) | ≥10(封顶) | **14** |
| 200KClaude | ≥10(封顶) | ≥10(封顶) | ≥10(封顶) |
| 1MGemini / Minimax | ≥10(封顶) | ≥10(封顶) | ≥10(封顶) |
---
@@ -114,7 +114,7 @@ maxSafeCoins = floor((budget - staticTokens) / perCoinTokens)
```go
const (
MaxCandidateCoins = 50 // UI 硬限制:用户最多设定的候选币数量
MaxCandidateCoins = 10 // UI 硬限制:用户最多设定的候选币数量
MaxPositions = 3 // 最大同时持仓数
MaxTimeframes = 4 // 最大时间框架数
MinKlineCount = 10 // 最少 K 线数
@@ -122,11 +122,11 @@ const (
)
```
### 为什么 MaxCandidateCoins = 50
### 为什么 MaxCandidateCoins = 10
- **默认配置**下 50 枚币约用 **~8,000 tokens**~6% of 131K),完全安全
- **极端配置**(4TF + 全指标)50 枚币会超过 131K 限制,但 **runtime token-blocking** 会在分析前拦截并报错
- 因此 50 是合理的 UI 上限:一方面给用户足够灵活性,另一方面依赖运行时保护防止真正的溢出
- **默认配置**下 10 枚币约用 **~15,000 tokens**~12% of 131K),完全安全
- **极端配置**(4TF + 全指标)10 枚币约用 **~60,000 tokens**~46% of 131K),仍有充足余量
- 因此 10 是保守且安全的 UI 上限:在所有模型和配置组合下均不会触发 token 限制
### 建议使用范围
+25 -9
View File
@@ -602,16 +602,28 @@ type ModelLimit struct {
Level string `json:"level"` // "ok" | "warning" | "danger"
}
// Context window sizes (tokens) for each model family
const (
contextLimitDeepSeek = 131_072 // 128K
contextLimitOpenAI = 128_000 // 128K
contextLimitClaude = 200_000 // 200K
contextLimitQwen = 131_072 // 128K
contextLimitGemini = 1_000_000 // 1M
contextLimitGrok = 131_072 // 128K
contextLimitKimi = 131_072 // 128K
contextLimitMinimax = 1_000_000 // 1M
)
// ModelContextLimits maps provider names to their context window sizes (in tokens)
var ModelContextLimits = map[string]int{
"deepseek": 131072,
"openai": 128000,
"claude": 200000,
"qwen": 131072,
"gemini": 1000000,
"grok": 131072,
"kimi": 131072,
"minimax": 1000000,
"deepseek": contextLimitDeepSeek,
"openai": contextLimitOpenAI,
"claude": contextLimitClaude,
"qwen": contextLimitQwen,
"gemini": contextLimitGemini,
"grok": contextLimitGrok,
"kimi": contextLimitKimi,
"minimax": contextLimitMinimax,
}
// GetContextLimit returns the context limit for a given provider
@@ -619,7 +631,7 @@ func GetContextLimit(provider string) int {
if limit, ok := ModelContextLimits[provider]; ok {
return limit
}
return 131072 // safe default
return contextLimitDeepSeek // safe default
}
// GetContextLimitForClient returns context limit for a provider+model pair.
@@ -639,6 +651,10 @@ func GetContextLimitForClient(provider, model string) int {
return ModelContextLimits["kimi"]
case strings.HasPrefix(model, "qwen"):
return ModelContextLimits["qwen"]
case strings.HasPrefix(model, "minimax"):
return ModelContextLimits["minimax"]
case strings.HasPrefix(model, "deepseek"):
return ModelContextLimits["deepseek"]
default:
return ModelContextLimits["deepseek"]
}
+4 -2
View File
@@ -323,8 +323,8 @@ function App() {
const selectedTrader = traders?.find((t) => t.trader_id === selectedTraderId)
const effectiveAccount = account
const effectivePositions = (positionsPollOff && !positions) ? [] as Position[] : positions
const effectiveDecisions = (decisionsPollOff && !decisions) ? [] as DecisionRecord[] : decisions
const effectivePositions = positions
const effectiveDecisions = decisions
// Handle routing
useEffect(() => {
@@ -544,7 +544,9 @@ function App() {
account={effectiveAccount}
accountFailed={accountPollOff}
positions={effectivePositions}
positionsFailed={positionsPollOff}
decisions={effectiveDecisions}
decisionsFailed={decisionsPollOff}
decisionsLimit={decisionsLimit}
onDecisionsLimitChange={setDecisionsLimit}
stats={stats}
+9
View File
@@ -1167,6 +1167,9 @@ export const translations = {
close: 'Close',
showingPositions: 'Showing {shown} of {total} positions',
perPage: 'Per page',
accountFetchFailed: 'DATA_FETCH::FAILED — Account data unavailable, check connection',
positionsFetchFailed: 'Position data unavailable',
decisionsFetchFailed: 'Decision data unavailable',
},
// AITradersPage toast messages
@@ -2467,6 +2470,9 @@ export const translations = {
close: '平仓',
showingPositions: '显示 {shown} / {total} 个持仓',
perPage: '每页',
accountFetchFailed: 'DATA_FETCH::FAILED — 账户数据请求失败,请检查连接',
positionsFetchFailed: '持仓数据请求失败',
decisionsFetchFailed: '决策记录请求失败',
},
aiTradersToast: {
@@ -3570,6 +3576,9 @@ export const translations = {
close: 'Tutup',
showingPositions: 'Menampilkan {shown} dari {total} posisi',
perPage: 'Per halaman',
accountFetchFailed: 'DATA_FETCH::FAILED — Data akun tidak tersedia, periksa koneksi',
positionsFetchFailed: 'Data posisi tidak tersedia',
decisionsFetchFailed: 'Data keputusan tidak tersedia',
},
aiTradersToast: {
+15 -1
View File
@@ -105,7 +105,9 @@ interface TraderDashboardPageProps {
account?: AccountInfo
accountFailed?: boolean
positions?: Position[]
positionsFailed?: boolean
decisions?: DecisionRecord[]
decisionsFailed?: boolean
decisionsLimit: number
onDecisionsLimitChange: (limit: number) => void
stats?: Statistics
@@ -120,7 +122,9 @@ export function TraderDashboardPage({
account,
accountFailed,
positions,
positionsFailed,
decisions,
decisionsFailed,
decisionsLimit,
onDecisionsLimitChange,
lastUpdate,
@@ -491,7 +495,7 @@ export function TraderDashboardPage({
<span>PNL::{account.total_pnl?.toFixed(2)}</span>
</div>
) : accountFailed ? (
<span style={{ color: '#F6465D' }}>DATA_FETCH::FAILED </span>
<span style={{ color: '#F6465D' }}>{t('traderDashboard.accountFetchFailed', language)}</span>
) : (
<div className="flex gap-4">
<span className="inline-block w-32 h-3 rounded bg-white/5 animate-pulse" />
@@ -723,6 +727,11 @@ export function TraderDashboardPage({
</div>
)}
</div>
) : positionsFailed ? (
<div className="text-center py-16 text-nofx-text-muted opacity-60">
<div className="text-4xl mb-4"></div>
<div className="text-lg font-semibold mb-2">{t('traderDashboard.positionsFetchFailed', language)}</div>
</div>
) : (
<div className="text-center py-16 text-nofx-text-muted opacity-60">
<div className="text-6xl mb-4 opacity-50 grayscale">📊</div>
@@ -776,6 +785,11 @@ export function TraderDashboardPage({
decisions.map((decision, i) => (
<DecisionCard key={i} decision={decision} language={language} onSymbolClick={handleSymbolClick} />
))
) : decisionsFailed ? (
<div className="py-16 text-center text-nofx-text-muted opacity-60">
<div className="text-4xl mb-4"></div>
<div className="text-lg font-semibold mb-2">{t('traderDashboard.decisionsFetchFailed', language)}</div>
</div>
) : (
<div className="py-16 text-center text-nofx-text-muted opacity-60">
<div className="text-6xl mb-4 opacity-30 grayscale">🧠</div>