chore: 移除不再使用的 ChanMacro、system、tests。

这些目录已废弃,从仓库中清理。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 18:11:29 +08:00
co-authored by Cursor
parent f2e77e1bdb
commit e2e45bc1bc
51 changed files with 0 additions and 6172 deletions
-189
View File
@@ -1,189 +0,0 @@
"""
fetchers/breadth.py — Fetches TOP50 OHLCV and computes market breadth metrics.
Multi-tier: Top20 / Top30 / Top50 for advance/decline, EMA20%, new highs, BTC.D.
"""
from datetime import date as Date, datetime
from typing import Optional
import logging
import pandas as pd
import numpy as np
import requests
from .base import BaseFetcher
from config import config
class BreadthFetcher(BaseFetcher):
"""Fetches TOP50 coin OHLCV data and computes breadth metrics."""
def __init__(self, provider_url: Optional[str] = None):
super().__init__(timeout=60, max_retries=3)
self.provider_url = provider_url or config.provider_url
self.symbols = config.top50_symbols
self.ema_period = config.breadth_ema_period
self.new_high_window = config.breadth_new_high_window
self.logger = logging.getLogger(__name__)
def fetch(self, target_date: Optional[Date] = None) -> dict:
"""
Fetch daily OHLCV for all TOP50 symbols and compute breadth.
Returns a dict suitable for storing in breadth_daily table.
"""
if target_date is None:
target_date = Date.today()
# Fetch last 60 days of daily data for each symbol to compute EMAs and new highs
all_data = {}
for symbol in self.symbols:
try:
df = self._fetch_symbol(symbol)
if df is not None and not df.empty:
all_data[symbol] = df
except Exception as e:
self.logger.debug(f"Failed to fetch {symbol}: {e}")
if not all_data:
self.logger.error("No symbol data fetched for breadth")
return {}
# Compute breadth metrics for the target date
breadth = self._compute_breadth(all_data, target_date)
return breadth
def _fetch_symbol(self, symbol: str) -> Optional[pd.DataFrame]:
"""Fetch daily OHLCV for a single symbol."""
url = f"{self.provider_url}/api/candles"
params = {
"symbol": symbol,
"tf": "1d",
"limit": 100,
}
try:
resp = requests.get(url, params=params, timeout=15)
resp.raise_for_status()
data = resp.json()
if not data:
return None
df = pd.DataFrame(data)
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df["date"] = df["timestamp"].dt.date
df = df.drop_duplicates(subset="date").sort_values("date").reset_index(drop=True)
df["close"] = df["close"].astype(float)
df["ema20"] = df["close"].ewm(span=self.ema_period, adjust=False).mean()
return df
except Exception:
return None
def _compute_breadth(self, all_data: dict, target_date: Date) -> dict:
"""Compute breadth metrics for a specific date across all symbols."""
total = len(all_data)
advances_50 = declines_50 = 0
above_ema20_50 = 0
new_highs_50 = 0
advances_30 = declines_30 = 0
above_ema20_30 = 0
new_highs_30 = 0
advances_20 = declines_20 = 0
above_ema20_20 = 0
new_highs_20 = 0
for i, (symbol, df) in enumerate(all_data.items()):
# Get data for target date
df["date_str"] = df["date"].astype(str)
target_str = str(target_date)
idx = df[df["date_str"] == target_str].index
if len(idx) == 0:
continue
row_idx = idx[0]
if row_idx < 1:
continue
current_close = df.loc[row_idx, "close"]
prev_close = df.loc[row_idx - 1, "close"]
# Advance/Decline
if current_close > prev_close:
if i < 50: advances_50 += 1
if i < 30: advances_30 += 1
if i < 20: advances_20 += 1
elif current_close < prev_close:
if i < 50: declines_50 += 1
if i < 30: declines_30 += 1
if i < 20: declines_20 += 1
# Above EMA20
ema20_val = df.loc[row_idx, "ema20"]
if not pd.isna(ema20_val) and current_close > ema20_val:
if i < 50: above_ema20_50 += 1
if i < 30: above_ema20_30 += 1
if i < 20: above_ema20_20 += 1
# New 20-day highs
lookback_start = max(0, row_idx - self.new_high_window)
recent_highs = df.loc[lookback_start:row_idx - 1, "high"].astype(float)
current_high = df.loc[row_idx, "high"]
if len(recent_highs) > 0 and float(current_high) > recent_highs.max():
if i < 50: new_highs_50 += 1
if i < 30: new_highs_30 += 1
if i < 20: new_highs_20 += 1
return {
"date": str(target_date),
"total_tracked": total,
"advance_top50": advances_50,
"decline_top50": declines_50,
"above_ema20_top50": above_ema20_50,
"new_highs_20d_top50": new_highs_50,
"advance_top30": advances_30,
"advance_top20": advances_20,
"above_ema20_top30": above_ema20_30,
"above_ema20_top20": above_ema20_20,
"new_highs_20d_top30": new_highs_30,
"new_highs_20d_top20": new_highs_20,
"btc_dominance": None, # Reserved for Coinglass API integration
}
def store(self, db_path: Optional[str] = None, record: Optional[dict] = None) -> int:
"""Store a breadth record into SQLite. Returns 1 if inserted/updated."""
import sqlite3
db_path = db_path or config.db_path
conn = sqlite3.connect(db_path)
if record is None:
conn.close()
return 0
try:
conn.execute("""
INSERT OR REPLACE INTO breadth_daily
(date, total_tracked,
advance_top50, decline_top50, above_ema20_top50, new_highs_20d_top50,
advance_top30, advance_top20,
above_ema20_top30, above_ema20_top20,
new_highs_20d_top30, new_highs_20d_top20,
btc_dominance)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
record["date"], record.get("total_tracked", 50),
record.get("advance_top50", 0), record.get("decline_top50", 0),
record.get("above_ema20_top50", 0), record.get("new_highs_20d_top50", 0),
record.get("advance_top30", 0), record.get("advance_top20", 0),
record.get("above_ema20_top30", 0), record.get("above_ema20_top20", 0),
record.get("new_highs_20d_top30", 0), record.get("new_highs_20d_top20", 0),
record.get("btc_dominance"),
))
conn.commit()
return 1
except Exception as e:
self.logger.error(f"Failed to store breadth: {e}")
return 0
finally:
conn.close()