mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-09 22:10:57 +08:00
Analysis of 410 live closed trades found the edge is real but was being destroyed by execution: gross +$267 vs $245 fees; trades held <1h were net negative (the <15m bucket alone: -$48 on $66 fees) while 1h+ holds carried +$78; shorts lost $72 while longs made $94 — and both the prompt and the engine were manufacturing those losers. Changes, each tied to the data: - Prompt: removed the 'MUST open at least one long AND one short every cycle' mandate (forced weak-signal shorts); direction is now data-driven with 'never open just to balance the book'. Added fee-awareness (round trip ≈ 0.1% notional, require expected move ≥ 3x cost) and aligned the hold discipline with the backend throttle. - Forced book-balance opens now require |board z-score| ≥ 0.75 — the engine previously force-opened full-size 10x positions on near-neutral signals with hardcoded confidence 70. DirectionalCandidates now carries scores. - Min AI-managed hold raised 45m → 60m (the 15-60m bucket still bled after the earlier throttle landed). - Legacy prompt hygiene: vergex path drops long-only-era custom prompts and zh-era configs fall back wholesale to built-in English sections — fixes the two long-failing kernel prompt tests. - New Edge Profile dashboard panel: net after fees by hold-time bucket and side, computed from recent closed trades, with an automatic takeaway line — the fee/churn regression detector, always visible. Fixed the HistoricalPosition timestamp types (epoch ms, not strings).
43 lines
1.4 KiB
Go
43 lines
1.4 KiB
Go
package kernel
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"nofx/provider/vergex"
|
|
)
|
|
|
|
func TestDirectionalCandidates(t *testing.T) {
|
|
e := &StrategyEngine{
|
|
vergexRankingCache: map[string]*vergex.SignalRankItem{
|
|
"xyz:NVDA": {Symbol: "xyz:NVDA", Bias: "bullish", Rank: 2, Score: 1.07},
|
|
"xyz:AAPL": {Symbol: "xyz:AAPL", Bias: "bullish", Rank: 1, Score: 1.76},
|
|
"BTC": {Symbol: "BTC", Bias: "bearish", Rank: 3, Score: -0.9},
|
|
"ETH": {Symbol: "ETH", Bias: "bearish", Rank: 1, Score: -0.05},
|
|
"SOL": {Symbol: "SOL", Bias: "neutral", Rank: 1, Score: 0.4},
|
|
},
|
|
}
|
|
|
|
bull, bear := e.DirectionalCandidates()
|
|
|
|
if len(bull) != 2 || bull[0].Symbol != "xyz:AAPL" || bull[1].Symbol != "xyz:NVDA" {
|
|
t.Fatalf("bullish should be rank-ordered [xyz:AAPL xyz:NVDA], got %v", bull)
|
|
}
|
|
if bull[0].Score != 1.76 || bull[1].Score != 1.07 {
|
|
t.Fatalf("bullish candidates should carry their board scores, got %v", bull)
|
|
}
|
|
if len(bear) != 2 || bear[0].Symbol != "ETH" || bear[1].Symbol != "BTC" {
|
|
t.Fatalf("bearish should be rank-ordered [ETH BTC], got %v", bear)
|
|
}
|
|
if bear[0].Score != -0.05 || bear[1].Score != -0.9 {
|
|
t.Fatalf("bearish candidates should carry their board scores, got %v", bear)
|
|
}
|
|
}
|
|
|
|
func TestDirectionalCandidatesEmpty(t *testing.T) {
|
|
e := &StrategyEngine{}
|
|
bull, bear := e.DirectionalCandidates()
|
|
if len(bull) != 0 || len(bear) != 0 {
|
|
t.Fatalf("empty cache should yield no candidates, got %v %v", bull, bear)
|
|
}
|
|
}
|