feat: 新增 ChanMacro 宏观 regime 检测模块

This commit is contained in:
jackyu66git
2026-08-20 16:03:25 +08:00
parent 340676bfbd
commit 29cff47f98
48 changed files with 5846 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
"""
fetchers/base.py — Abstract base class for all macro data fetchers.
Provides retry logic, rate limiting, and a common interface.
"""
from abc import ABC, abstractmethod
from datetime import date as Date
from typing import Optional
import logging
import time
import requests
class BaseFetcher(ABC):
"""Abstract base for all macro data fetchers."""
def __init__(self, timeout: int = 30, max_retries: int = 3):
self.timeout = timeout
self.max_retries = max_retries
self.logger = logging.getLogger(self.__class__.__name__)
def _get(self, url: str, params: Optional[dict] = None,
headers: Optional[dict] = None) -> dict:
"""GET with retry and exponential backoff."""
for attempt in range(self.max_retries):
try:
resp = requests.get(
url, params=params, headers=headers, timeout=self.timeout
)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
wait = 2 ** attempt
self.logger.warning(
f"Request failed (attempt {attempt+1}/{self.max_retries}): {e}. "
f"Retrying in {wait}s"
)
if attempt < self.max_retries - 1:
time.sleep(wait)
else:
raise
def _get_raw(self, url: str, params: Optional[dict] = None,
headers: Optional[dict] = None) -> bytes:
"""GET raw bytes with retry (for non-JSON endpoints)."""
for attempt in range(self.max_retries):
try:
resp = requests.get(
url, params=params, headers=headers, timeout=self.timeout
)
resp.raise_for_status()
return resp.content
except requests.RequestException as e:
wait = 2 ** attempt
if attempt < self.max_retries - 1:
time.sleep(wait)
else:
raise
@abstractmethod
def fetch(self, target_date: Optional[Date] = None) -> list[dict]:
"""Fetch raw data. Returns list of record dicts."""
...
@abstractmethod
def store(self, db_path: str, records: list[dict]) -> int:
"""Store raw records into SQLite. Returns count of new rows."""
...