mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-03 19:11:02 +08:00
221 lines
7.0 KiB
Go
221 lines
7.0 KiB
Go
package vergex
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseSignalRankingAndFilterTradFiItems(t *testing.T) {
|
|
body := []byte(`{
|
|
"data": {
|
|
"rankings": [
|
|
{"marketType":"hip3-perp","symbol":"AAPL","bias":"long","confidence":0.88,"compositeZ":1.75},
|
|
{"marketType":"stock","symbol":"NVDA","bias":"long","confidence":0.81,"compositeZ":1.25},
|
|
{"market_type":"core_perp","symbol":"BTC","bias":"short","score":0.91}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
ranking, err := ParseSignalRanking(body)
|
|
if err != nil {
|
|
t.Fatalf("ParseSignalRanking returned error: %v", err)
|
|
}
|
|
if len(ranking.Items) != 3 {
|
|
t.Fatalf("items len = %d, want 3", len(ranking.Items))
|
|
}
|
|
if ranking.Items[0].Symbol != "AAPL" || ranking.Items[0].MarketType != "hip3-perp" || ranking.Items[0].Bias != "long" {
|
|
t.Fatalf("unexpected first item: %+v", ranking.Items[0])
|
|
}
|
|
|
|
items := FilterTradFiItems(ranking.Items, "hip3_perp", 5)
|
|
if len(items) != 2 {
|
|
t.Fatalf("filtered len = %d, want 2", len(items))
|
|
}
|
|
if got := TradableSymbol(items[0].Symbol); got != "xyz:AAPL" {
|
|
t.Fatalf("TradableSymbol = %q, want xyz:AAPL", got)
|
|
}
|
|
if got := TradableSymbol(items[1].Symbol); got != "xyz:NVDA" {
|
|
t.Fatalf("TradableSymbol = %q, want xyz:NVDA", got)
|
|
}
|
|
}
|
|
|
|
func TestFilterTradFiItemsAllowsFullClaw402Board(t *testing.T) {
|
|
items := make([]SignalRankItem, 0, 35)
|
|
for i := 1; i <= 35; i++ {
|
|
items = append(items, SignalRankItem{
|
|
Rank: i,
|
|
Symbol: fmt.Sprintf("xyz:STK%02d", i),
|
|
MarketType: "hip3_perp",
|
|
Bias: "bullish",
|
|
})
|
|
}
|
|
|
|
filtered := FilterTradFiItems(items, "hip3_perp", 30)
|
|
if len(filtered) != 30 {
|
|
t.Fatalf("filtered len = %d, want 30", len(filtered))
|
|
}
|
|
if filtered[0].Symbol != "STK01" || filtered[29].Symbol != "STK30" {
|
|
t.Fatalf("unexpected filtered bounds: first=%q last=%q", filtered[0].Symbol, filtered[29].Symbol)
|
|
}
|
|
|
|
capped := FilterTradFiItems(items, "hip3_perp", 35)
|
|
if len(capped) != MaxSignalRankingItems {
|
|
t.Fatalf("capped len = %d, want %d", len(capped), MaxSignalRankingItems)
|
|
}
|
|
}
|
|
|
|
func TestParseSignalRankingReadsNestedMarketShape(t *testing.T) {
|
|
body := []byte(`{
|
|
"data": {
|
|
"items": [
|
|
{"market":{"marketType":"hip3_perp","symbol":"xyz:NBIS"},"symbol":"xyz:NBIS","bias":"bullish","compositeZ":1.05,"rank":5},
|
|
{"market":{"marketType":"hip3_perp","symbol":"xyz:DRAM"},"symbol":"xyz:DRAM","bias":"bullish","compositeZ":0.47,"rank":10},
|
|
{"market":{"marketType":"core_perp","symbol":"BTC"},"symbol":"BTC","bias":"bearish","compositeZ":-0.08,"rank":12}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
ranking, err := ParseSignalRanking(body)
|
|
if err != nil {
|
|
t.Fatalf("ParseSignalRanking returned error: %v", err)
|
|
}
|
|
allItems := FilterSignalRankingItems(ranking.Items, "all", 30)
|
|
if len(allItems) != 3 {
|
|
t.Fatalf("all filtered len = %d, want 3: %+v", len(allItems), allItems)
|
|
}
|
|
if allItems[2].Symbol != "BTC" || allItems[2].MarketType != "core_perp" || allItems[2].Category != "crypto" {
|
|
t.Fatalf("unexpected crypto item: %+v", allItems[2])
|
|
}
|
|
items := FilterTradFiItems(ranking.Items, "hip3_perp", 30)
|
|
if len(items) != 2 {
|
|
t.Fatalf("filtered len = %d, want 2: %+v", len(items), items)
|
|
}
|
|
if items[0].Symbol != "NBIS" || items[0].MarketType != "hip3_perp" {
|
|
t.Fatalf("unexpected first item: %+v", items[0])
|
|
}
|
|
if items[1].Symbol != "DRAM" || items[1].MarketType != "hip3_perp" {
|
|
t.Fatalf("unexpected second item: %+v", items[1])
|
|
}
|
|
}
|
|
|
|
func TestMarketSymbolPreservesHIP3XYZPrefix(t *testing.T) {
|
|
if got := MarketSymbol("hip3_perp", "INTC"); got != "xyz:INTC" {
|
|
t.Fatalf("MarketSymbol hip3_perp/INTC = %q, want xyz:INTC", got)
|
|
}
|
|
if got := MarketSymbol("hip3_perp", "xyz:skhx"); got != "xyz:SKHX" {
|
|
t.Fatalf("MarketSymbol hip3_perp/xyz:skhx = %q, want xyz:SKHX", got)
|
|
}
|
|
if got := MarketSymbol("core_perp", "BTC"); got != "BTC" {
|
|
t.Fatalf("MarketSymbol core_perp/BTC = %q, want BTC", got)
|
|
}
|
|
if got := TradableSymbolForMarket("core_perp", "BTC"); got != "BTC" {
|
|
t.Fatalf("TradableSymbolForMarket core_perp/BTC = %q, want BTC", got)
|
|
}
|
|
if got := TradableSymbolForMarket("hip3_perp", "INTC"); got != "xyz:INTC" {
|
|
t.Fatalf("TradableSymbolForMarket hip3_perp/INTC = %q, want xyz:INTC", got)
|
|
}
|
|
}
|
|
|
|
func TestAddQueryDefaultsUsesClaw402GatewayParams(t *testing.T) {
|
|
params := url.Values{}
|
|
addQueryDefaults(params, Query{
|
|
MarketType: "hip3_perp",
|
|
Symbol: "INTC",
|
|
Chain: "hyperliquid",
|
|
LiqBand: "15",
|
|
}, true)
|
|
|
|
if got := params.Get("marketType"); got != "hip3_perp" {
|
|
t.Fatalf("marketType = %q", got)
|
|
}
|
|
if got := params.Get("symbol"); got != "xyz:INTC" {
|
|
t.Fatalf("symbol = %q, want xyz:INTC", got)
|
|
}
|
|
if got := params.Get("chain"); got != "mainnet" {
|
|
t.Fatalf("chain = %q, want mainnet", got)
|
|
}
|
|
if got := params.Get("liqBand"); got != "15" {
|
|
t.Fatalf("liqBand = %q, want 15", got)
|
|
}
|
|
}
|
|
|
|
func TestQueryChainMapsHyperliquidToVergexMainnet(t *testing.T) {
|
|
if got := QueryChain("hyperliquid"); got != "mainnet" {
|
|
t.Fatalf("QueryChain hyperliquid = %q, want mainnet", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatAnalysisForAIIncludesDetailErrors(t *testing.T) {
|
|
text := FormatAnalysisForAI(&MarketAnalysis{
|
|
Symbol: "xyz:NVDA",
|
|
QuerySymbol: "NVDA",
|
|
MarketType: "stock",
|
|
SignalLabError: "upstream returned status 502",
|
|
HeatmapError: "market not found",
|
|
})
|
|
|
|
if !containsAll(text, "Signal Lab: unavailable", "upstream returned status 502", "Cost/Liquidation Heatmap: unavailable", "market not found") {
|
|
t.Fatalf("formatted analysis did not include detail errors:\n%s", text)
|
|
}
|
|
}
|
|
|
|
func TestFormatAnalysisForAIFormatsVergexDetailsAsMarkdown(t *testing.T) {
|
|
text := FormatAnalysisForAI(&MarketAnalysis{
|
|
Symbol: "xyz:DRAM",
|
|
QuerySymbol: "DRAM",
|
|
MarketType: "hip3_perp",
|
|
SignalLab: []byte(`{
|
|
"data": {
|
|
"band": "15",
|
|
"bias": "bullish",
|
|
"compositeZ": 1.41,
|
|
"confidence": "Medium",
|
|
"dimensions": [
|
|
{
|
|
"family": "I Cost & Positioning",
|
|
"label": "Capital-gains overhang",
|
|
"direction": "bullish",
|
|
"strength": "medium",
|
|
"percentile": 80,
|
|
"detail": "price is above aggregate cost"
|
|
}
|
|
]
|
|
}
|
|
}`),
|
|
Heatmap: []byte(`{
|
|
"data": {
|
|
"binStep": 3.2,
|
|
"bins": [
|
|
{"bucketStartPrice": 100, "bucketEndPrice": 103.2, "longCost": 1200000, "shortCost": 1000, "longLiq": 5000, "shortLiq": 700000},
|
|
{"bucketStartPrice": 103.2, "bucketEndPrice": 106.4, "longCost": 1000, "shortCost": 2000, "longLiq": 900000, "shortLiq": 4000}
|
|
]
|
|
}
|
|
}`),
|
|
})
|
|
|
|
if !containsAll(text,
|
|
"#### Signal Lab",
|
|
"| Family | Signal | Direction | Strength | Percentile | Detail |",
|
|
"Capital-gains overhang",
|
|
"#### Cost/Liquidation Heatmap",
|
|
"| Price zone | Long cost | Short cost | Long liq | Short liq | Main cluster |",
|
|
"$1.20M",
|
|
) {
|
|
t.Fatalf("formatted analysis is not markdown enough:\n%s", text)
|
|
}
|
|
if strings.Contains(text, "Signal Lab: {") || strings.Contains(text, "Cost/Liquidation Heatmap: {") {
|
|
t.Fatalf("formatted analysis still includes raw inline JSON:\n%s", text)
|
|
}
|
|
}
|
|
|
|
func containsAll(text string, needles ...string) bool {
|
|
for _, needle := range needles {
|
|
if !strings.Contains(text, needle) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|