mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-15 16:56:56 +08:00
feat(market): add data staleness detection (Part 2/3) (#800)
* feat(market): add data staleness detection
## 問題背景
解決 PR #703 Part 2: 數據陳舊性檢測
- 修復 DOGEUSDT 式問題:連續價格不變表示數據源異常
- 防止系統處理僵化/過期的市場數據
## 技術方案
### 數據陳舊性檢測 (market/data.go)
- **函數**: `isStaleData(klines []Kline, symbol string) bool`
- **檢測邏輯**:
- 連續 5 個 3 分鐘週期價格完全不變(15 分鐘無波動)
- 價格波動容忍度:0.01%(避免誤報)
- 成交量檢查:價格凍結 + 成交量為 0 → 確認陳舊
- **處理策略**:
- 數據陳舊確認:跳過該幣種,返回錯誤
- 極低波動市場:記錄警告但允許通過(價格穩定但有成交量)
### 調用時機
- 在 `Get()` 函數中,獲取 3m K線後立即檢測
- 早期返回:避免後續無意義的計算和 API 調用
## 實現細節
- **檢測閾值**: 5 個連續週期
- **容忍度**: 0.01% 價格波動
- **日誌**: 英文國際化版本
- **並發安全**: 函數無狀態,安全
## 影響範圍
- ✅ 修改 market/data.go: 新增 isStaleData() + 調用邏輯
- ✅ 新增 log 包導入
- ✅ 50 行新增代碼
## 測試建議
1. 模擬 DOGEUSDT 場景:連續價格不變 + 成交量為 0
2. 驗證日誌輸出:`stale data confirmed: price freeze + zero volume`
3. 正常市場:極低波動但有成交量,應允許通過並記錄警告
## 相關 Issue/PR
- 拆分自 **PR #703** (Part 2/3)
- 基於最新 upstream/dev (3112250)
- 依賴: 無
- 前置: Part 1 (OI 時間序列) - 已提交 PR #798
- 後續: Part 3 (手續費率傳遞)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* test(market): add comprehensive unit tests for isStaleData function
- Test normal fluctuating data (expects non-stale)
- Test price freeze with zero volume (expects stale)
- Test price freeze with volume (low volatility market)
- Test insufficient data edge case (<5 klines)
- Test boundary conditions (exactly 5 klines)
- Test tolerance threshold (0.01% price change)
- Test mixed scenario (normal → freeze transition)
- Test empty klines edge case
All 8 test cases passed.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
Co-authored-by: Shui <88711385+hzb1115@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
dbb05f7fde
commit
e0b4d026d3
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -35,6 +36,12 @@ func Get(symbol string) (*Data, error) {
|
||||
return nil, fmt.Errorf("获取3分钟K线失败: %v", err)
|
||||
}
|
||||
|
||||
// Data staleness detection: Prevent DOGEUSDT-style price freeze issues
|
||||
if isStaleData(klines3m, symbol) {
|
||||
log.Printf("⚠️ WARNING: %s detected stale data (consecutive price freeze), skipping symbol", symbol)
|
||||
return nil, fmt.Errorf("%s data is stale, possible cache failure", symbol)
|
||||
}
|
||||
|
||||
// 获取4小时K线数据 (最近10个)
|
||||
klines4h, err = WSMonitorCli.GetCurrentKlines(symbol, "4h") // 多获取用于计算指标
|
||||
if err != nil {
|
||||
@@ -541,3 +548,47 @@ func parseFloat(v interface{}) (float64, error) {
|
||||
return 0, fmt.Errorf("unsupported type: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// isStaleData detects stale data (consecutive price freeze)
|
||||
// Fix DOGEUSDT-style issue: consecutive N periods with completely unchanged prices indicate data source anomaly
|
||||
func isStaleData(klines []Kline, symbol string) bool {
|
||||
if len(klines) < 5 {
|
||||
return false // Insufficient data to determine
|
||||
}
|
||||
|
||||
// Detection threshold: 5 consecutive 3-minute periods with unchanged price (15 minutes without fluctuation)
|
||||
const stalePriceThreshold = 5
|
||||
const priceTolerancePct = 0.0001 // 0.01% fluctuation tolerance (avoid false positives)
|
||||
|
||||
// Take the last stalePriceThreshold K-lines
|
||||
recentKlines := klines[len(klines)-stalePriceThreshold:]
|
||||
firstPrice := recentKlines[0].Close
|
||||
|
||||
// Check if all prices are within tolerance
|
||||
for i := 1; i < len(recentKlines); i++ {
|
||||
priceDiff := math.Abs(recentKlines[i].Close-firstPrice) / firstPrice
|
||||
if priceDiff > priceTolerancePct {
|
||||
return false // Price fluctuation exists, data is normal
|
||||
}
|
||||
}
|
||||
|
||||
// Additional check: MACD and volume
|
||||
// If price is unchanged but MACD/volume shows normal fluctuation, it might be a real market situation (extremely low volatility)
|
||||
// Check if volume is also 0 (data completely frozen)
|
||||
allVolumeZero := true
|
||||
for _, k := range recentKlines {
|
||||
if k.Volume > 0 {
|
||||
allVolumeZero = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allVolumeZero {
|
||||
log.Printf("⚠️ %s stale data confirmed: price freeze + zero volume", symbol)
|
||||
return true
|
||||
}
|
||||
|
||||
// Price frozen but has volume: might be extremely low volatility market, allow but log warning
|
||||
log.Printf("⚠️ %s detected extreme price stability (no fluctuation for %d consecutive periods), but volume is normal", symbol, stalePriceThreshold)
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user