Files
nofx/store/strategy_token_test.go
T
deanokk f0d3352971 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>
2026-03-27 00:26:40 +08:00

113 lines
3.5 KiB
Go

package store
import "testing"
func TestEstimateTokens_DefaultConfig(t *testing.T) {
config := GetDefaultStrategyConfig("en")
est := config.EstimateTokens()
if est.Total <= 0 {
t.Errorf("expected positive token estimate, got %d", est.Total)
}
if est.Total > 200000 {
t.Errorf("token estimate %d seems unreasonably high for default config", est.Total)
}
// Breakdown should sum approximately to total (before 15% margin)
subtotal := est.Breakdown.SystemPrompt + est.Breakdown.MarketData +
est.Breakdown.RankingData + est.Breakdown.QuantData + est.Breakdown.FixedOverhead
expectedTotal := subtotal * 115 / 100
if est.Total != expectedTotal {
t.Errorf("total %d != breakdown subtotal %d * 1.15 = %d", est.Total, subtotal, expectedTotal)
}
// Should have model limits
if len(est.ModelLimits) == 0 {
t.Error("expected model limits to be populated")
}
// Default config should be ok for all models
for _, ml := range est.ModelLimits {
if ml.Level == "danger" {
t.Errorf("default config should not exceed %s limit, got %d%%", ml.Name, ml.UsagePct)
}
}
}
func TestEstimateTokens_ZhVsEn(t *testing.T) {
enConfig := GetDefaultStrategyConfig("en")
zhConfig := GetDefaultStrategyConfig("zh")
enEst := enConfig.EstimateTokens()
zhEst := zhConfig.EstimateTokens()
// Chinese config should have more tokens for system prompt due to CJK encoding
// but total can vary — just ensure both are reasonable
if enEst.Total <= 0 || zhEst.Total <= 0 {
t.Errorf("both estimates should be positive: en=%d, zh=%d", enEst.Total, zhEst.Total)
}
}
func TestEstimateTokens_HighConfig(t *testing.T) {
config := GetDefaultStrategyConfig("en")
// Push config to extremes (beyond clamped limits)
config.CoinSource.SourceType = "static"
config.CoinSource.StaticCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "DOGEUSDT", "XRPUSDT"}
config.Indicators.Klines.SelectedTimeframes = []string{"1m", "3m", "5m", "15m", "1h", "4h"}
config.Indicators.Klines.PrimaryCount = 100
config.Indicators.EnableEMA = true
config.Indicators.EnableMACD = true
config.Indicators.EnableRSI = true
config.Indicators.EnableATR = true
config.Indicators.EnableBOLL = true
est := config.EstimateTokens()
// Should produce a higher estimate than default
defaultCfg := GetDefaultStrategyConfig("en")
defaultEst := defaultCfg.EstimateTokens()
if est.Total <= defaultEst.Total {
t.Errorf("high config estimate %d should be greater than default %d", est.Total, defaultEst.Total)
}
// Should have some models in warning/danger
hasDanger := false
for _, ml := range est.ModelLimits {
if ml.Level == "danger" || ml.Level == "warning" {
hasDanger = true
break
}
}
// With 5 coins * 6 timeframes * 100 klines, this should exceed small models
if !hasDanger {
t.Logf("high config estimate: %d tokens", est.Total)
}
}
func TestGetContextLimit(t *testing.T) {
if got := GetContextLimit("deepseek"); got != 131072 {
t.Errorf("deepseek limit = %d, want 131072", got)
}
if got := GetContextLimit("unknown_provider"); got != 131072 {
t.Errorf("unknown provider should return default 131072, got %d", got)
}
}
func TestGetEffectiveCoinCount(t *testing.T) {
config := StrategyConfig{
CoinSource: CoinSourceConfig{
SourceType: "static",
StaticCoins: []string{"BTCUSDT", "ETHUSDT"},
},
}
if got := config.getEffectiveCoinCount(); got != 2 {
t.Errorf("static coin count = %d, want 2", got)
}
config.CoinSource.SourceType = "ai500"
config.CoinSource.AI500Limit = 5
if got := config.getEffectiveCoinCount(); got != 5 {
t.Errorf("ai500 coin count = %d, want 5", got)
}
}