feat(market): Phase 1 扩展 — 更多 symbol + Bollinger/Vegas + 衍生品信号
- collector.symbols 与 supportedSymbols 加 SOL/BNB/DOGE,env-default 与 README 同步 - entity 追加 TechnicalStructure.Intervals(每周期 Bollinger + Vegas,omitempty 不破坏既有字段)与 DerivativesBundle.Signal - 新增纯解析 usecase derivatives_signal.go:fundingBias 绝对阈值、oiSignal 用 P20 baseline + 价格方向(OI up + 价格 up/down → long/short_building,funding 不参与方向)、lsrRegime 绝对阈值;signal 数据不足走 warnings 进 DataQuality - indicator.go 加 sma/stddev/ema/bollinger/vegas + computeIntervalTechnicals;IndicatorComputer 签名收 klines map - harness 同步:G12 扩入 derivatives_signal.go(含 Makefile G12.5/6 + ADR-0002 适用范围 + project-map / harness-health 更新)
This commit is contained in:
@@ -491,7 +491,7 @@ func TestNearestKlineIndex(t *testing.T) {
|
||||
|
||||
func TestCompute_EmptyInputs(t *testing.T) {
|
||||
u := NewIndicatorUsecase()
|
||||
out := u.Compute(nil, nil)
|
||||
out := u.Compute(nil, "1h", nil)
|
||||
require.NotNil(t, out.Support)
|
||||
require.NotNil(t, out.Resistance)
|
||||
require.Empty(t, out.Support)
|
||||
@@ -499,6 +499,8 @@ func TestCompute_EmptyInputs(t *testing.T) {
|
||||
require.Nil(t, out.RangeHigh)
|
||||
require.Nil(t, out.RangeLow)
|
||||
require.Nil(t, out.LongShortLine)
|
||||
require.NotNil(t, out.Intervals)
|
||||
require.Empty(t, out.Intervals)
|
||||
}
|
||||
|
||||
func TestCompute_SmokeWithVShape(t *testing.T) {
|
||||
@@ -519,10 +521,113 @@ func TestCompute_SmokeWithVShape(t *testing.T) {
|
||||
strconv.FormatFloat(p+0.25, 'f', -1, 64),
|
||||
)
|
||||
}
|
||||
out := u.Compute(klines, nil)
|
||||
out := u.Compute(map[string][]entity.Kline{"1h": klines}, "1h", nil)
|
||||
require.Len(t, out.Support, 1)
|
||||
require.Empty(t, out.Resistance)
|
||||
require.NotNil(t, out.RangeHigh)
|
||||
require.NotNil(t, out.RangeLow)
|
||||
require.Nil(t, out.LongShortLine, "no LSR data → nil")
|
||||
require.Contains(t, out.Intervals, "1h")
|
||||
}
|
||||
|
||||
// ---- Bollinger / Vegas -------------------------------------------------
|
||||
|
||||
// flatCloses 生成 n 根价位恒为 v 的 closes(用于 Squeeze=tight 验证)。
|
||||
func flatCloses(n int, v float64) []float64 {
|
||||
out := make([]float64, n)
|
||||
for i := range out {
|
||||
out[i] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ascCloses 生成等差递增 closes:base, base+step, ...
|
||||
func ascCloses(n int, base, step float64) []float64 {
|
||||
out := make([]float64, n)
|
||||
for i := range out {
|
||||
out[i] = base + float64(i)*step
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestBollinger_FlatSeries_Tight(t *testing.T) {
|
||||
bb := bollinger(flatCloses(20, 100))
|
||||
require.NotNil(t, bb)
|
||||
require.Equal(t, "100", bb.Mid)
|
||||
require.Equal(t, "100", bb.Upper)
|
||||
require.Equal(t, "100", bb.Lower)
|
||||
require.Equal(t, "0", bb.BandwidthPct)
|
||||
require.Equal(t, "tight", bb.Squeeze)
|
||||
}
|
||||
|
||||
func TestBollinger_AscendingSeries_Expanded(t *testing.T) {
|
||||
// 0..29 等差,末 20 根 mid=19.5、σ ≈ 5.77、bw ≈ 118%
|
||||
bb := bollinger(ascCloses(30, 0, 1))
|
||||
require.NotNil(t, bb)
|
||||
require.Equal(t, "expanded", bb.Squeeze)
|
||||
mid, _ := strconv.ParseFloat(bb.Mid, 64)
|
||||
require.InDelta(t, 19.5, mid, 1e-9)
|
||||
}
|
||||
|
||||
func TestBollinger_InsufficientData(t *testing.T) {
|
||||
require.Nil(t, bollinger(flatCloses(19, 100)))
|
||||
require.Nil(t, bollinger(nil))
|
||||
}
|
||||
|
||||
func TestVegas_BullishStack(t *testing.T) {
|
||||
// 上升序列 → EMA12 > EMA144 > EMA169,趋势 bull
|
||||
v := vegas(ascCloses(200, 100, 1))
|
||||
require.NotNil(t, v)
|
||||
e12, _ := strconv.ParseFloat(v.EMA12, 64)
|
||||
e144, _ := strconv.ParseFloat(v.EMA144, 64)
|
||||
e169, _ := strconv.ParseFloat(v.EMA169, 64)
|
||||
require.Greater(t, e12, e144)
|
||||
require.Greater(t, e144, e169)
|
||||
require.Equal(t, "bull", v.Trend)
|
||||
}
|
||||
|
||||
func TestVegas_BearishStack(t *testing.T) {
|
||||
// 下降序列
|
||||
v := vegas(ascCloses(200, 300, -1))
|
||||
require.NotNil(t, v)
|
||||
require.Equal(t, "bear", v.Trend)
|
||||
}
|
||||
|
||||
func TestVegas_FlatRange(t *testing.T) {
|
||||
// 平盘:三条线相等
|
||||
v := vegas(flatCloses(200, 100))
|
||||
require.NotNil(t, v)
|
||||
require.Equal(t, "range", v.Trend)
|
||||
}
|
||||
|
||||
func TestVegas_InsufficientData(t *testing.T) {
|
||||
require.Nil(t, vegas(ascCloses(168, 100, 1)))
|
||||
}
|
||||
|
||||
func TestCompute_IntervalsPopulated(t *testing.T) {
|
||||
u := NewIndicatorUsecase()
|
||||
// 给 1h(25 根) 与 4h(200 根) 喂上升序列
|
||||
mk := func(n int) []entity.Kline {
|
||||
out := make([]entity.Kline, n)
|
||||
for i := 0; i < n; i++ {
|
||||
p := 100 + float64(i)
|
||||
out[i] = mkKline(int64(i),
|
||||
strconv.FormatFloat(p, 'f', -1, 64),
|
||||
strconv.FormatFloat(p+0.5, 'f', -1, 64),
|
||||
strconv.FormatFloat(p, 'f', -1, 64),
|
||||
strconv.FormatFloat(p+0.25, 'f', -1, 64),
|
||||
)
|
||||
}
|
||||
return out
|
||||
}
|
||||
out := u.Compute(map[string][]entity.Kline{
|
||||
"1h": mk(25),
|
||||
"4h": mk(200),
|
||||
}, "1h", nil)
|
||||
require.Contains(t, out.Intervals, "1h")
|
||||
require.Contains(t, out.Intervals, "4h")
|
||||
require.NotNil(t, out.Intervals["1h"].Bollinger, "25 closes 够算 BB")
|
||||
require.Nil(t, out.Intervals["1h"].Vegas, "25 closes 不够算 EMA169")
|
||||
require.NotNil(t, out.Intervals["4h"].Bollinger)
|
||||
require.NotNil(t, out.Intervals["4h"].Vegas)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user