168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from typing import Literal
|
||
|
||
import yaml
|
||
from pydantic import BaseModel, Field, ValidationError
|
||
|
||
|
||
class AppConfig(BaseModel):
|
||
host: str = "0.0.0.0"
|
||
port: int = 8088
|
||
poll_interval_seconds: int = 120
|
||
log_file: str = "./runtime/system.log"
|
||
database_url: str = "sqlite+aiosqlite:///./runtime/alerts.db"
|
||
session_secret: str = "please-change-me"
|
||
|
||
|
||
class AuthConfig(BaseModel):
|
||
"""
|
||
enabled: 为 false 时跳过登录(仅建议纯局域网、无外网暴露时使用)。
|
||
"""
|
||
|
||
enabled: bool = True
|
||
username: str
|
||
password: str
|
||
|
||
|
||
class WeComConfig(BaseModel):
|
||
webhook: str
|
||
mentioned_mobile_list: list[str] = Field(default_factory=list)
|
||
|
||
|
||
class GateConfig(BaseModel):
|
||
"""Gate.io 公共 REST v4(USDT 永续 settle=usdt)。"""
|
||
|
||
api_base: str = "https://api.gateio.ws/api/v4"
|
||
settle: str = "usdt"
|
||
quote_currency: str = "USDT"
|
||
|
||
|
||
class ProxyConfig(BaseModel):
|
||
"""
|
||
出站 HTTP 客户端代理(httpx),用于访问 Gate 等外网。
|
||
企业微信与本机/局域网 Ollama(Gemma)默认直连,不使用此配置。
|
||
可写 socks5h://…;程序在交给 httpx 时会自动改为 socks5://(避免 Unknown scheme)。
|
||
"""
|
||
|
||
enabled: bool = False
|
||
url: str = "socks5h://127.0.0.1:1080"
|
||
|
||
|
||
class OrderExecutorConfig(BaseModel):
|
||
"""
|
||
与 gate_order_executor 联动:企微突破推送 **成功之后**,向执行器 POST /v1/signal。
|
||
请求不走 proxy.url(直连 base_url),便于同机 127.0.0.1。
|
||
webhook_secret 须与执行器 config.yaml 的 security.webhook_secret 一致。
|
||
"""
|
||
|
||
enabled: bool = False
|
||
base_url: str = "http://127.0.0.1:8090"
|
||
webhook_secret: str = ""
|
||
timeout_seconds: float = Field(15.0, ge=3.0, le=120.0)
|
||
|
||
|
||
class WatchSymbol(BaseModel):
|
||
"""Gate USDT 永续 base 资产符号,如 BTC、ORDI、1000PEPE(与合约名 BTC_USDT 的左侧一致)。"""
|
||
|
||
symbol: str
|
||
|
||
|
||
class MonitorConfig(BaseModel):
|
||
"""
|
||
监控侧过滤。
|
||
universe:
|
||
- all_swaps: 监控 Gate 全部 USDT 本位线性永续中,24h 成交额达标的合约(不依赖 watch_symbols)。
|
||
- watchlist: 仅监控 watch_symbols 中列出且满足成交额阈值的标的。
|
||
min_24h_quote_volume_usdt: 近 24h 成交额下限(USDT)。优先使用 Gate ticker 的 volume_24h_quote。
|
||
all_swaps 模式下若设为 0 或负数,将拒绝整轮扫描(避免无阈值拉全市场)。
|
||
watchlist 模式下 0 表示关闭成交额过滤。
|
||
btc_daily_gate_enabled: 可选;true 时仍计算 BTC 日线 regime 供面板/日志参考,不再拦截山寨扫描。
|
||
btc_sideways_lookback_days / btc_sideways_max_range_pct: 与上述辅助门控配套的横盘区分参数。
|
||
"""
|
||
|
||
universe: Literal["all_swaps", "watchlist"] = "all_swaps"
|
||
min_24h_quote_volume_usdt: float = 10_000_000
|
||
# 可选:BTC 日线 regime 仅展示/记录;推送门控用「近8h×15m BTC 环境(横盘则多空均可;否则涨→LONG、跌→SHORT)+ 本币4h同向」
|
||
btc_daily_gate_enabled: bool = True
|
||
btc_sideways_lookback_days: int = 14
|
||
btc_sideways_max_range_pct: float = 10.0
|
||
# 同一币种在 N 小时内对同一条「链路」只落库一条告警、只推送一次(0 表示关闭去重)
|
||
# 链路含:GATE-USDT 5m WATCH / GATE-USDT 5m TRIGGER(分级)与 FUNNEL-GEMMA(漏斗)
|
||
symbol_signal_dedupe_hours: float = 4.0
|
||
# 企业微信主推送(突破预警):仅对本轮监控池内 24h 成交额排名前 N 的合约推送;0 表示不限制
|
||
wecom_push_max_volume_rank: int = 30
|
||
|
||
|
||
class GemmaConfig(BaseModel):
|
||
"""
|
||
本地 Ollama 跑 Gemma(或其它模型)做漏斗二次分拣。
|
||
需在机器上自行启动 ollama 并拉取模型;开启后仅对本轮 5m 扫描命中的 WATCH/TRIGGER 按成交额取前 N 再请求。
|
||
"""
|
||
|
||
enabled: bool = False
|
||
ollama_base_url: str = "http://127.0.0.1:11434"
|
||
model: str = "gemma2:2b"
|
||
timeout_seconds: float = 180.0
|
||
temperature: float = 0.15
|
||
json_mode: bool = True
|
||
send_chart_image: bool = True
|
||
max_funnel_per_cycle: int = 12
|
||
vision_top_n: int = 4
|
||
gemma_push_priority_min: float = 7.0
|
||
composite_push_min: float = 72.0
|
||
|
||
|
||
class DailyReportConfig(BaseModel):
|
||
"""每日晨报:北京时间定时生成昨天复盘,并可推送企业微信。"""
|
||
|
||
enabled: bool = True
|
||
run_time_cn: str = "08:30"
|
||
push_wecom: bool = True
|
||
run_on_startup: bool = False
|
||
|
||
|
||
class Settings(BaseModel):
|
||
app: AppConfig
|
||
auth: AuthConfig
|
||
wecom: WeComConfig
|
||
gate: GateConfig
|
||
proxy: ProxyConfig = Field(default_factory=ProxyConfig)
|
||
order_executor: OrderExecutorConfig = Field(default_factory=OrderExecutorConfig)
|
||
monitor: MonitorConfig = Field(default_factory=MonitorConfig)
|
||
gemma: GemmaConfig = Field(default_factory=GemmaConfig)
|
||
daily_report: DailyReportConfig = Field(default_factory=DailyReportConfig)
|
||
watch_symbols: list[WatchSymbol] = Field(default_factory=list)
|
||
|
||
|
||
def load_settings(config_path: str = "config.yaml") -> Settings:
|
||
path = Path(config_path).expanduser().resolve()
|
||
if not path.exists():
|
||
raise FileNotFoundError(
|
||
f"配置文件不存在: {path}. 请先复制 config.example.yaml 为 config.yaml 并填写密钥。"
|
||
)
|
||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||
try:
|
||
return Settings.model_validate(raw)
|
||
except ValidationError as exc:
|
||
raise ValueError(f"配置文件校验失败: {exc}") from exc
|
||
|
||
|
||
# 兼容原 OKX 风格 bar 字符串(映射见 app.gate._to_gate_interval)
|
||
GATE_BAR_CHOICES: tuple[str, ...] = (
|
||
"1m",
|
||
"3m",
|
||
"5m",
|
||
"15m",
|
||
"30m",
|
||
"1H",
|
||
"2H",
|
||
"4H",
|
||
"6H",
|
||
"12H",
|
||
"1D",
|
||
"1W",
|
||
"1M",
|
||
)
|