mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 04:50:57 +08:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user