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