Files
nofx/kernel/engine_directional_test.go
tinkle-community c53a563bff feat: data-driven autopilot profitability tuning + edge profile panel
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).
2026-07-07 19:21:49 +09:00

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)
}
}