mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-21 19:27:39 +08:00
Live decomposition of the losing streak: 23% win rate with avg win +$1.23 / avg loss -$1.04 on ~0.3-0.5% price moves, where the ~0.14% round-trip fee ate 30-50% of every tiny winner. Death by small-move grinding. The AI was closing positions on ±0.5% noise after the 60m min-hold, capping winners at ~0.86%. Redesign to 'few big positions, held for big moves': - Throttle: min hold 60m->4h, noise-close window 90m->8h, reentry 30m->3h, opens/hour 30->3, opens/cycle 6->2. Noise band widened -1%..+2% -> -4%..+6% so small moves can no longer trigger a close. - Exits: stop bypass -2.5% -> -5%, take-profit bypass +5% -> +12% (wide, asymmetric — let winners run, cut losers only on a real move). - Leverage 20x -> 5x: a -5% stop at 20x is instant liquidation; at 5x it is -25% of margin, survivable. 2 positions x 2.5x = 5x total (full margin, ~20% cushion) instead of 4x5x=20x. - Prompt now instructs the AI to set wide stops (~-5%) and distant targets (~+10-12%), hold multi-hour, and never scalp 0.5% moves. Live strategy updated (maxPos=2, lev=5, ratio=2.5).
125 lines
3.9 KiB
Go
125 lines
3.9 KiB
Go
package kernel
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"nofx/store"
|
|
)
|
|
|
|
func TestBuildSystemPromptUsesVergexClaw402Prompt(t *testing.T) {
|
|
cfg := store.GetDefaultStrategyConfig("zh")
|
|
cfg.CoinSource.SourceType = "vergex_signal"
|
|
cfg.CoinSource.VergexLimit = 5
|
|
cfg.PromptSections.RoleDefinition = "# You are a professional Hyperliquid USDC multi-asset trading AI"
|
|
cfg.CustomPrompt = "Long only, no shorts."
|
|
|
|
engine := NewStrategyEngine(&cfg)
|
|
prompt := engine.BuildSystemPrompt(30, "balanced")
|
|
|
|
if !strings.Contains(prompt, "NOFX Claw402 auto-trader") {
|
|
t.Fatalf("prompt did not use the Claw402/Vergex TradeFi role:\n%s", prompt)
|
|
}
|
|
if !strings.Contains(prompt, "Claw402.ai Signal Ranking") || !strings.Contains(prompt, "Signal Lab") || !strings.Contains(prompt, "Cost/Liquidation Heatmap") {
|
|
t.Fatalf("prompt is missing Claw402/Vergex detail data guidance:\n%s", prompt)
|
|
}
|
|
if !strings.Contains(prompt, "open_short") {
|
|
t.Fatalf("prompt should explicitly allow short entries:\n%s", prompt)
|
|
}
|
|
if !strings.Contains(prompt, "Direction must be data-driven") {
|
|
t.Fatalf("prompt should explain that direction is data-driven, not long-only:\n%s", prompt)
|
|
}
|
|
if !strings.Contains(prompt, "every open position must use exactly 5x") {
|
|
t.Fatalf("prompt should force 5x leverage for Claw402 opens:\n%s", prompt)
|
|
}
|
|
if !strings.Contains(prompt, "use the full max notional per position") {
|
|
t.Fatalf("prompt should force full-size Claw402 opens:\n%s", prompt)
|
|
}
|
|
if containsCJK(prompt) {
|
|
t.Fatalf("system prompt must be English-only, got CJK text:\n%s", prompt)
|
|
}
|
|
legacyPhrases := []string{
|
|
"Hyperliquid USDC multi-asset trading AI",
|
|
"Long only",
|
|
"Altcoin",
|
|
"BTC/ETH",
|
|
"LONG-ONLY",
|
|
"Do not short",
|
|
"MUST open a long",
|
|
}
|
|
for _, phrase := range legacyPhrases {
|
|
if strings.Contains(prompt, phrase) {
|
|
t.Fatalf("prompt still contains legacy phrase %q:\n%s", phrase, prompt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildSystemPromptFallsBackToEnglishWhenConfiguredLanguageIsChinese(t *testing.T) {
|
|
cfg := store.GetDefaultStrategyConfig("zh")
|
|
cfg.CoinSource.SourceType = "static"
|
|
cfg.CoinSource.StaticCoins = []string{"BTCUSDT", "ETHUSDT"}
|
|
cfg.CoinSource.VergexLimit = 0
|
|
cfg.CoinSource.VergexMarketType = ""
|
|
cfg.CoinSource.VergexChain = ""
|
|
cfg.PromptSections.RoleDefinition = "# You are a Chinese system prompt"
|
|
cfg.PromptSections.TradingFrequency = "# High-frequency trading\nTrade every minute."
|
|
cfg.PromptSections.EntryStandards = "# Entry\nOpen positions freely."
|
|
cfg.PromptSections.DecisionProcess = "# Decision\nOutput directly."
|
|
cfg.CustomPrompt = "Chinese preference should not enter the system prompt."
|
|
|
|
engine := NewStrategyEngine(&cfg)
|
|
prompt := engine.BuildSystemPrompt(30, "balanced")
|
|
|
|
required := []string{
|
|
"Data Dictionary & Trading Rules",
|
|
"You are a professional Hyperliquid USDC multi-asset trading AI",
|
|
"Trading Frequency Awareness",
|
|
"Entry Standards",
|
|
"Decision Process",
|
|
}
|
|
for _, phrase := range required {
|
|
if !strings.Contains(prompt, phrase) {
|
|
t.Fatalf("English fallback prompt missing %q:\n%s", phrase, prompt)
|
|
}
|
|
}
|
|
if containsCJK(prompt) {
|
|
t.Fatalf("system prompt must be English-only, got CJK text:\n%s", prompt)
|
|
}
|
|
}
|
|
|
|
func TestBuildSystemPromptDoesNotForceLongOnlyForSingleXYZ(t *testing.T) {
|
|
prompt := buildXYZStockCustomPrompt("XYZ:INTC")
|
|
|
|
required := []string{
|
|
"DIRECTIONAL, SIGNAL-DRIVEN",
|
|
"You may open long or short",
|
|
"open_short",
|
|
}
|
|
for _, phrase := range required {
|
|
if !strings.Contains(prompt, phrase) {
|
|
t.Fatalf("single XYZ prompt missing %q:\n%s", phrase, prompt)
|
|
}
|
|
}
|
|
|
|
forbidden := []string{
|
|
"LONG-ONLY",
|
|
"Do not short",
|
|
"MUST open a long",
|
|
"Probing > waiting",
|
|
}
|
|
for _, phrase := range forbidden {
|
|
if strings.Contains(prompt, phrase) {
|
|
t.Fatalf("single XYZ prompt still contains forced-long phrase %q:\n%s", phrase, prompt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func containsCJK(text string) bool {
|
|
for _, r := range text {
|
|
if r >= 0x4E00 && r <= 0x9FFF {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|