feat: 添加OKX行情接入+趋势检测+累积变动系统+界面重构
- 新增OKX WebSocket行情连接器,扩展4交易所价格监控 - 新增z-score趋势检测引擎(TrendDetector),识别价格异动/趋势启动 - 新增累积变动跟踪(CumulativeTracker),基于1min/5min多交易所共识 - 趋势事件和累积变动事件持久化到SQLite - 新增Binance/OKX动量检测字段,扩展前端动量卡片至15列 - 迁移至macOS(darwin-arm64),更新前端依赖 - Dashboard网格重构:非交易卡片置顶,交易卡片置底 - TrackedCoin添加OK字段,添加ExBinance/ExOKX常量 - 前端新增趋势检测卡片、趋势历史卡片、累积变动卡片 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
047571921e
commit
b7767c95ae
+119
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMeanStdDev(t *testing.T) {
|
||||
changes := []exchangeChange{
|
||||
{name: "A", change: 0.1},
|
||||
{name: "B", change: 0.2},
|
||||
{name: "C", change: 0.3},
|
||||
{name: "D", change: 0.4},
|
||||
}
|
||||
mean, std := meanStdDev(changes)
|
||||
|
||||
if math.Abs(mean-0.25) > 0.001 {
|
||||
t.Errorf("mean = %.4f, want 0.2500", mean)
|
||||
}
|
||||
if math.Abs(std-0.1118) > 0.01 {
|
||||
t.Errorf("std = %.4f, want ~0.1118", std)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeanStdDevSingleValue(t *testing.T) {
|
||||
changes := []exchangeChange{
|
||||
{name: "A", change: 0.1},
|
||||
}
|
||||
mean, std := meanStdDev(changes)
|
||||
if mean != 0.1 {
|
||||
t.Errorf("mean = %.4f, want 0.1000", mean)
|
||||
}
|
||||
if std != 0 {
|
||||
t.Errorf("std = %.4f, want 0.0000", std)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeanStdDevZeroValues(t *testing.T) {
|
||||
changes := []exchangeChange{
|
||||
{name: "A", change: 0},
|
||||
{name: "B", change: 0},
|
||||
}
|
||||
mean, std := meanStdDev(changes)
|
||||
if mean != 0 {
|
||||
t.Errorf("mean = %.4f, want 0.0000", mean)
|
||||
}
|
||||
if std != 0 {
|
||||
t.Errorf("std = %.4f, want 0.0000", std)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeanStdDevEmpty(t *testing.T) {
|
||||
mean, std := meanStdDev(nil)
|
||||
if mean != 0 || std != 0 {
|
||||
t.Errorf("expected 0,0 for empty input, got %.4f, %.4f", mean, std)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZScoreCalculation(t *testing.T) {
|
||||
// One exchange strongly diverging from the others
|
||||
// Three exchanges nearly flat, one moves 1.5%
|
||||
changes := []exchangeChange{
|
||||
{name: "BG", change: 0.01},
|
||||
{name: "HL", change: 0.01},
|
||||
{name: "BN", change: 0.02},
|
||||
{name: "OK", change: 1.50}, // anomalous!
|
||||
}
|
||||
_, std := meanStdDev(changes)
|
||||
maxAbs := 1.50
|
||||
zScore := maxAbs / std
|
||||
|
||||
if zScore < 2.0 {
|
||||
t.Errorf("z-score = %.2f, expected > 2.0 for divergent exchange", zScore)
|
||||
}
|
||||
t.Logf("Divergent exchange (1 of 4): z-score = %.2f (std = %.4f)", zScore, std)
|
||||
}
|
||||
|
||||
func TestCoordinatedMovement(t *testing.T) {
|
||||
// All exchanges moving together = also a trend (consensus, not anomaly)
|
||||
changes := []exchangeChange{
|
||||
{name: "BG", change: 0.05},
|
||||
{name: "HL", change: 0.06},
|
||||
{name: "BN", change: 0.04},
|
||||
{name: "OK", change: 0.07},
|
||||
}
|
||||
_, std := meanStdDev(changes)
|
||||
maxAbs := 0.07
|
||||
zScore := maxAbs / std
|
||||
|
||||
// Tight cluster → std is small, z-score is high → valid trend signal
|
||||
t.Logf("Co-movement: z-score = %.2f (std = %.4f) — high z-score + 4/4 agreement = trend", zScore, std)
|
||||
}
|
||||
|
||||
func TestTrendDetectorConfigure(t *testing.T) {
|
||||
td := NewTrendDetector(nil)
|
||||
if td.anomalyMul != 3.0 {
|
||||
t.Errorf("default anomalyMul = %.1f, want 3.0", td.anomalyMul)
|
||||
}
|
||||
if td.confirmTicks != 3 {
|
||||
t.Errorf("default confirmTicks = %d, want 3", td.confirmTicks)
|
||||
}
|
||||
|
||||
td.Configure(300, 2.5, 2, 30000)
|
||||
if td.anomalyMul != 2.5 {
|
||||
t.Errorf("anomalyMul after configure = %.1f, want 2.5", td.anomalyMul)
|
||||
}
|
||||
if td.confirmTicks != 2 {
|
||||
t.Errorf("confirmTicks after configure = %d, want 2", td.confirmTicks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrendDetectorEmptyConfigure(t *testing.T) {
|
||||
td := NewTrendDetector(nil)
|
||||
// Passing zeros should keep defaults
|
||||
td.Configure(0, 0, 0, 0)
|
||||
if td.anomalyMul != 3.0 {
|
||||
t.Errorf("anomalyMul = %.1f, expected default 3.0", td.anomalyMul)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user