feat(market): add GetBoxData for multi-period box calculation

Adds calculateBoxData internal function and GetBoxData public API that
fetches 1h klines and computes three Donchian box levels (short/mid/long).
This will be used by the grid trading system to detect market regime.
This commit is contained in:
tinkle-community
2026-01-17 21:47:07 +08:00
parent 7d7493b576
commit bd8cc9c176
2 changed files with 84 additions and 0 deletions

View File

@@ -1242,3 +1242,59 @@ func calculateDonchian(klines []Kline, period int) (upper, lower float64) {
func ExportCalculateDonchian(klines []Kline, period int) (float64, float64) {
return calculateDonchian(klines, period)
}
// Box period constants (in 1h candles)
const (
ShortBoxPeriod = 72 // 3 days of 1h candles
MidBoxPeriod = 240 // 10 days of 1h candles
LongBoxPeriod = 500 // ~21 days of 1h candles
)
// calculateBoxData calculates multi-period box data from klines
func calculateBoxData(klines []Kline, currentPrice float64) *BoxData {
box := &BoxData{
CurrentPrice: currentPrice,
}
if len(klines) == 0 {
return box
}
box.ShortUpper, box.ShortLower = calculateDonchian(klines, ShortBoxPeriod)
box.MidUpper, box.MidLower = calculateDonchian(klines, MidBoxPeriod)
box.LongUpper, box.LongLower = calculateDonchian(klines, LongBoxPeriod)
return box
}
// ExportCalculateBoxData exports calculateBoxData for testing
func ExportCalculateBoxData(klines []Kline, currentPrice float64) *BoxData {
return calculateBoxData(klines, currentPrice)
}
// GetBoxData fetches 1h klines and calculates box data for a symbol
func GetBoxData(symbol string) (*BoxData, error) {
symbol = Normalize(symbol)
// Fetch 500 1h klines
var klines []Kline
var err error
if IsXyzDexAsset(symbol) {
klines, err = getKlinesFromHyperliquid(symbol, "1h", LongBoxPeriod)
} else {
klines, err = getKlinesFromCoinAnk(symbol, "1h", LongBoxPeriod)
}
if err != nil {
return nil, fmt.Errorf("failed to get 1h klines: %w", err)
}
if len(klines) == 0 {
return nil, fmt.Errorf("no kline data available")
}
currentPrice := klines[len(klines)-1].Close
return calculateBoxData(klines, currentPrice), nil
}

View File

@@ -555,3 +555,31 @@ func TestCalculateDonchian_InvalidPeriod(t *testing.T) {
t.Errorf("Expected (0, 0) for negative period, got (%v, %v)", upper, lower)
}
}
func TestCalculateBoxData(t *testing.T) {
// Create synthetic kline data
klines := make([]Kline, 500)
for i := 0; i < 500; i++ {
basePrice := 100.0
klines[i] = Kline{
High: basePrice + float64(i%10),
Low: basePrice - float64(i%10),
Close: basePrice,
}
}
box := ExportCalculateBoxData(klines, 100.0)
if box.ShortUpper == 0 || box.ShortLower == 0 {
t.Error("Short box should not be zero")
}
if box.MidUpper == 0 || box.MidLower == 0 {
t.Error("Mid box should not be zero")
}
if box.LongUpper == 0 || box.LongLower == 0 {
t.Error("Long box should not be zero")
}
if box.CurrentPrice != 100.0 {
t.Errorf("Expected CurrentPrice = 100.0, got %v", box.CurrentPrice)
}
}