fix: prevent DeepSeek token overflow with product-level limits (#1431)

* feat: enforce strategy limits to prevent token overflow

* fix: tune token limits after real-world testing

- Relax kline max 20→30, timeframes 3→4 (tested ~41K tokens, safe under 131K)
- Restore ranking limits to original [5,10,15,20] options (only ~1.5K token impact)
- Add static coins limit (max 3) with toast notification
- Add timeframe limit toast when exceeding 4
- Log SSE token usage (prompt/completion/total) from API response
- Fix nil logger crash in claw402 data client (engine.go)

* feat: add token estimation functionality for strategy configurations

* feat: add discard changes button in Strategy Studio for unsaved modifications

* feat: retain selected strategy after saving in Strategy Studio

* feat: enhance strategy display in Strategy Studio with improved layout and sorting of token limits

* refactor: improve layout and styling of stats display in CompetitionPage

* refactor: replace select elements with NofxSelect component for improved consistency in strategy configuration forms

* style: update NofxSelect component to use smaller text size for improved readability

* feat: implement token overflow handling in strategy updates and UI

---------

Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
deanokk
2026-03-27 00:26:40 +08:00
committed by GitHub
parent af6f6d5930
commit f0d3352971
20 changed files with 1124 additions and 338 deletions

View File

@@ -1,6 +1,7 @@
import { Clock, Activity, TrendingUp, BarChart2, Info, Lock, ExternalLink, Zap, Check, AlertCircle, Key } from 'lucide-react'
import type { IndicatorConfig } from '../../types'
import { indicator, ts } from '../../i18n/strategy-translations'
import { NofxSelect } from '../ui/select'
// Default NofxOS API Key
const DEFAULT_NOFXOS_API_KEY = 'cm_568c67eae410d912c54c'
@@ -60,6 +61,16 @@ export function IndicatorEditor({
})
}
} else {
if (current.length >= 4) {
// Show toast notification
const toast = document.createElement('div')
toast.textContent = language === 'zh' ? '最多选择 4 个时间维度' : 'Maximum 4 timeframes allowed'
toast.className = 'fixed top-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-lg text-sm z-50 shadow-lg'
toast.style.cssText = 'background:#F6465D;color:#fff;'
document.body.appendChild(toast)
setTimeout(() => toast.remove(), 2000)
return
}
current.push(tf)
onChange({
...config,
@@ -299,26 +310,22 @@ export function IndicatorEditor({
<p className="text-[10px] mt-1" style={{ color: '#5E6673' }}>{ts(indicator.oiRankingDesc, language)}</p>
{config.enable_oi_ranking && (
<div className="flex gap-2 mt-2" onClick={(e) => e.stopPropagation()}>
<select
<NofxSelect
value={config.oi_ranking_duration || '1h'}
onChange={(e) => !disabled && onChange({ ...config, oi_ranking_duration: e.target.value })}
onChange={(val) => !disabled && onChange({ ...config, oi_ranking_duration: val })}
disabled={disabled}
className="flex-1 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
<option value="1h">1h</option>
<option value="4h">4h</option>
<option value="24h">24h</option>
</select>
<select
options={[{ value: '1h', label: '1h' }, { value: '4h', label: '4h' }, { value: '24h', label: '24h' }]}
/>
<NofxSelect
value={config.oi_ranking_limit || 10}
onChange={(e) => !disabled && onChange({ ...config, oi_ranking_limit: parseInt(e.target.value) })}
onChange={(val) => !disabled && onChange({ ...config, oi_ranking_limit: parseInt(val) })}
disabled={disabled}
className="w-14 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
{[5, 10, 15, 20].map(n => <option key={n} value={n}>{n}</option>)}
</select>
options={[5, 10, 15, 20].map(n => ({ value: n, label: String(n) }))}
/>
</div>
)}
</div>
@@ -359,26 +366,22 @@ export function IndicatorEditor({
<p className="text-[10px] mt-1" style={{ color: '#5E6673' }}>{ts(indicator.netflowRankingDesc, language)}</p>
{config.enable_netflow_ranking && (
<div className="flex gap-2 mt-2" onClick={(e) => e.stopPropagation()}>
<select
<NofxSelect
value={config.netflow_ranking_duration || '1h'}
onChange={(e) => !disabled && onChange({ ...config, netflow_ranking_duration: e.target.value })}
onChange={(val) => !disabled && onChange({ ...config, netflow_ranking_duration: val })}
disabled={disabled}
className="flex-1 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
<option value="1h">1h</option>
<option value="4h">4h</option>
<option value="24h">24h</option>
</select>
<select
options={[{ value: '1h', label: '1h' }, { value: '4h', label: '4h' }, { value: '24h', label: '24h' }]}
/>
<NofxSelect
value={config.netflow_ranking_limit || 10}
onChange={(e) => !disabled && onChange({ ...config, netflow_ranking_limit: parseInt(e.target.value) })}
onChange={(val) => !disabled && onChange({ ...config, netflow_ranking_limit: parseInt(val) })}
disabled={disabled}
className="w-14 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
{[5, 10, 15, 20].map(n => <option key={n} value={n}>{n}</option>)}
</select>
options={[5, 10, 15, 20].map(n => ({ value: n, label: String(n) }))}
/>
</div>
)}
</div>
@@ -419,27 +422,27 @@ export function IndicatorEditor({
<p className="text-[10px] mt-1" style={{ color: '#5E6673' }}>{ts(indicator.priceRankingDesc, language)}</p>
{config.enable_price_ranking && (
<div className="flex gap-2 mt-2" onClick={(e) => e.stopPropagation()}>
<select
<NofxSelect
value={config.price_ranking_duration || '1h,4h,24h'}
onChange={(e) => !disabled && onChange({ ...config, price_ranking_duration: e.target.value })}
onChange={(val) => !disabled && onChange({ ...config, price_ranking_duration: val })}
disabled={disabled}
className="flex-1 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
<option value="1h">1h</option>
<option value="4h">4h</option>
<option value="24h">24h</option>
<option value="1h,4h,24h">{ts(indicator.priceRankingMulti, language)}</option>
</select>
<select
options={[
{ value: '1h', label: '1h' },
{ value: '4h', label: '4h' },
{ value: '24h', label: '24h' },
{ value: '1h,4h,24h', label: ts(indicator.priceRankingMulti, language) },
]}
/>
<NofxSelect
value={config.price_ranking_limit || 10}
onChange={(e) => !disabled && onChange({ ...config, price_ranking_limit: parseInt(e.target.value) })}
onChange={(val) => !disabled && onChange({ ...config, price_ranking_limit: parseInt(val) })}
disabled={disabled}
className="w-14 px-2 py-1 rounded text-[10px]"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
>
{[5, 10, 15, 20].map(n => <option key={n} value={n}>{n}</option>)}
</select>
options={[5, 10, 15, 20].map(n => ({ value: n, label: String(n) }))}
/>
</div>
)}
</div>
@@ -515,7 +518,7 @@ export function IndicatorEditor({
}
disabled={disabled}
min={10}
max={200}
max={30}
className="w-16 px-2 py-1 rounded text-xs text-center"
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
/>