rebuild
This commit is contained in:
parent
d46f3e9801
commit
c9f9d1b2a0
2
.gitignore
vendored
2
.gitignore
vendored
@ -64,3 +64,5 @@ dist/
|
||||
archive/
|
||||
backups/
|
||||
tmp/
|
||||
legacy/scratch/
|
||||
reports/*.json
|
||||
|
||||
105
AGENTS.md
105
AGENTS.md
@ -11,7 +11,7 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
- 后端:`FastAPI`, `uvicorn`, `pydantic`
|
||||
- 数据与计算:`sqlite3`, `pandas`, `numpy`
|
||||
- 交易所/行情:`ccxt`, `requests`
|
||||
- 配置:`rules.yaml` + `config_loader.py`
|
||||
- 配置:`rules.yaml` + `app/config/config_loader.py`
|
||||
- 测试:`pytest` / `unittest`
|
||||
- 部署:`Dockerfile`, `docker-compose.yml`
|
||||
- 前端:`static/*.html` 模板页,由 FastAPI/Jinja2 提供页面壳和 API
|
||||
@ -22,28 +22,28 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
|
||||
建议把系统理解为 6 个层次:
|
||||
|
||||
1. `altcoin_screener.py`
|
||||
1. `app/services/altcoin_screener.py`
|
||||
负责粗筛,基于 Binance 行情、量价/结构等规则找候选币。
|
||||
2. `altcoin_confirm.py`
|
||||
2. `app/services/altcoin_confirm.py`
|
||||
负责确认,判断是否形成更可执行的机会,并生成入场计划、上下文和推送候选。
|
||||
3. `price_tracker.py`
|
||||
3. `app/services/price_tracker.py`
|
||||
负责跟踪活跃推荐,更新盈亏、止盈止损、趋势衰减、行动状态。
|
||||
4. `review_engine.py`
|
||||
4. `app/services/review_engine.py`
|
||||
负责复盘与策略自迭代,包括信号绩效、漏选复盘、规则候选、版本演进。
|
||||
5. `event_driven_screener.py`
|
||||
5. `app/services/event_driven_screener.py`
|
||||
负责事件/舆情驱动的快速触发检查,属于技术筛选主链路的补充入口。
|
||||
6. `web_server.py`
|
||||
6. `app/web/web_server.py`
|
||||
负责用户端和管理端 API、页面壳、订阅与认证相关接口。
|
||||
|
||||
### 3.2 数据与状态中心
|
||||
|
||||
- `altcoin_db.py` 是交易/推荐/状态的核心数据库层,体量很大,承担了:
|
||||
- `app/db/altcoin_db.py` 是交易/推荐/状态的核心数据库层,体量很大,承担了:
|
||||
- 初始化表结构
|
||||
- recommendation / screening_log / tracking / review 等主表读写
|
||||
- 推荐状态派生与展示口径整理
|
||||
- 部分状态迁移与兼容逻辑
|
||||
- `auth_db.py` 是会员、邀请码、邮箱验证、订阅、订单预留的数据库层。
|
||||
- `opportunity_lifecycle.py` 是机会生命周期和买点质量闸门的规则中心,决定:
|
||||
- `app/db/auth_db.py` 是会员、邀请码、邮箱验证、订阅、订单预留的数据库层。
|
||||
- `app/core/opportunity_lifecycle.py` 是机会生命周期和买点质量闸门的规则中心,决定:
|
||||
- 哪些机会只是观察池
|
||||
- 哪些机会可以进入“可即刻买入”
|
||||
- 哪些状态应该被视为历史/盈利管理/观察态
|
||||
@ -51,18 +51,20 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
### 3.3 配置中心
|
||||
|
||||
- `rules.yaml` 是策略配置单一事实源。
|
||||
- `config_loader.py` 负责:
|
||||
- `app/config/config_loader.py` 负责:
|
||||
- 读取/缓存配置
|
||||
- 暴露各子模块配置访问函数
|
||||
- 将部分复盘后的参数改写回 `rules.yaml`
|
||||
- 兼容旧信号名
|
||||
|
||||
如果要改筛选阈值、确认门槛、止盈止损、动态权重逻辑,优先检查 `rules.yaml` 和 `config_loader.py`,不要直接在业务脚本里硬编码新参数。
|
||||
如果要改筛选阈值、确认门槛、止盈止损、动态权重逻辑,优先检查 `rules.yaml` 和 `app/config/config_loader.py`,不要直接在业务脚本里硬编码新参数。
|
||||
|
||||
## 4. 目录速览
|
||||
|
||||
### 4.1 核心目录
|
||||
|
||||
- `/app`
|
||||
- 当前真实实现层,按职责拆成 `services`, `db`, `core`, `config`, `integrations`, `analysis`, `web`
|
||||
- `/static`
|
||||
- 页面文件,如 `app.html`, `auth.html`, `subscription.html`, `strategy.html`
|
||||
- `/tests`
|
||||
@ -71,6 +73,14 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
- 若干结构/状态机/信号时效性校验脚本
|
||||
- `/docker`
|
||||
- 容器入口与串行调度器
|
||||
- `/tools`
|
||||
- 非主链路工具脚本,如回测和输出摘要脚本
|
||||
- `/templates`
|
||||
- 被后端读取的 HTML 模板资源
|
||||
- `/reports`
|
||||
- 本地分析/回测结果产物
|
||||
- `/legacy`
|
||||
- 历史实验脚本、旧页面备份、临时整理归档
|
||||
- `/data`
|
||||
- SQLite 数据库存放目录
|
||||
- `/logs`
|
||||
@ -80,23 +90,36 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
|
||||
### 4.2 根目录关键文件
|
||||
|
||||
- [web_server.py](/Users/aaron/Desktop/code/alphax-docker/web_server.py)
|
||||
- [altcoin_db.py](/Users/aaron/Desktop/code/alphax-docker/altcoin_db.py)
|
||||
- [auth_db.py](/Users/aaron/Desktop/code/alphax-docker/auth_db.py)
|
||||
- [altcoin_screener.py](/Users/aaron/Desktop/code/alphax-docker/altcoin_screener.py)
|
||||
- [altcoin_confirm.py](/Users/aaron/Desktop/code/alphax-docker/altcoin_confirm.py)
|
||||
- [price_tracker.py](/Users/aaron/Desktop/code/alphax-docker/price_tracker.py)
|
||||
- [review_engine.py](/Users/aaron/Desktop/code/alphax-docker/review_engine.py)
|
||||
- [event_driven_screener.py](/Users/aaron/Desktop/code/alphax-docker/event_driven_screener.py)
|
||||
- [opportunity_lifecycle.py](/Users/aaron/Desktop/code/alphax-docker/opportunity_lifecycle.py)
|
||||
- [rules.yaml](/Users/aaron/Desktop/code/alphax-docker/rules.yaml)
|
||||
- [config_loader.py](/Users/aaron/Desktop/code/alphax-docker/config_loader.py)
|
||||
- [docker-compose.yml](/Users/aaron/Desktop/code/alphax-docker/docker-compose.yml)
|
||||
- [README_DOCKER.md](/Users/aaron/Desktop/code/alphax-docker/README_DOCKER.md)
|
||||
|
||||
### 4.3 根目录保留原则
|
||||
|
||||
根目录应尽量只保留这些类型:
|
||||
|
||||
- 顶层配置
|
||||
- Docker 入口与部署文件
|
||||
- 项目说明文档
|
||||
- 明确约定的非代码资产目录
|
||||
|
||||
Python 业务实现不应再直接留在根目录。
|
||||
|
||||
像下面这些内容应放在分层目录里:
|
||||
|
||||
- 服务流程:`/app/services`
|
||||
- 数据访问:`/app/db`
|
||||
- 领域与规则:`/app/core`
|
||||
- 配置访问:`/app/config`
|
||||
- Web 层:`/app/web`
|
||||
- 第三方集成:`/app/integrations`
|
||||
- 顶层配置
|
||||
- Docker 入口
|
||||
- 关键说明文档
|
||||
|
||||
## 5. Web/API 观察
|
||||
|
||||
`web_server.py` 体量非常大,既包含:
|
||||
`app/web/web_server.py` 体量非常大,既包含:
|
||||
|
||||
- 认证接口
|
||||
- 订阅接口
|
||||
@ -107,11 +130,11 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
|
||||
如果要改 Web 逻辑,先确认变更属于哪一类:
|
||||
|
||||
- “数据口径问题”优先去 `altcoin_db.py` / `opportunity_lifecycle.py`
|
||||
- “数据口径问题”优先去 `app/db/altcoin_db.py` / `app/core/opportunity_lifecycle.py`
|
||||
- “参数问题”优先去 `rules.yaml`
|
||||
- “页面展示问题”再回到 `static/*.html`
|
||||
|
||||
不要第一反应直接在 `web_server.py` 里堆业务分支,否则会继续放大这个文件的复杂度。
|
||||
不要第一反应直接在 `app/web/web_server.py` 里堆业务分支,否则会继续放大这个文件的复杂度。
|
||||
|
||||
## 6. 调度与运行方式
|
||||
|
||||
@ -135,16 +158,16 @@ AlphaX 是一个以 `Python + FastAPI + SQLite + 静态 HTML` 组成的加密市
|
||||
### 6.2 入口
|
||||
|
||||
- `docker/entrypoint.sh`
|
||||
- `web` -> 启动 uvicorn
|
||||
- `web` -> 启动 uvicorn (`app.web.web_server:app`)
|
||||
- `scheduler` -> 启动 `docker/scheduler.py`
|
||||
- `once` -> 执行单次脚本
|
||||
- `docker/scheduler.py`
|
||||
- 统一调度 `event_driven_screener.py`
|
||||
- `price_tracker.py`
|
||||
- `altcoin_confirm.py`
|
||||
- `altcoin_screener.py`
|
||||
- `sentiment_monitor.py`
|
||||
- `review_engine.py`
|
||||
- 统一调度 `app.services.event_driven_screener`
|
||||
- `app.services.price_tracker`
|
||||
- `app.services.altcoin_confirm`
|
||||
- `app.services.altcoin_screener`
|
||||
- `app.services.sentiment_monitor`
|
||||
- `app.services.review_engine`
|
||||
|
||||
## 7. 测试与验证建议
|
||||
|
||||
@ -184,10 +207,10 @@ python3 scripts/validate_signal_recency.py
|
||||
### 8.1 改动前先判断“应该改哪一层”
|
||||
|
||||
- 调参数:优先 `rules.yaml`
|
||||
- 配置读取/兼容:`config_loader.py`
|
||||
- 状态口径:`opportunity_lifecycle.py` 或 `altcoin_db.py`
|
||||
- DB 表结构/查询:`altcoin_db.py` / `auth_db.py`
|
||||
- API 契约:`web_server.py`
|
||||
- 配置读取/兼容:`app/config/config_loader.py`
|
||||
- 状态口径:`app/core/opportunity_lifecycle.py` 或 `app/db/altcoin_db.py`
|
||||
- DB 表结构/查询:`app/db/altcoin_db.py` / `app/db/auth_db.py`
|
||||
- API 契约:`app/web/web_server.py`
|
||||
- 页面壳和交互:`static/*.html`
|
||||
|
||||
### 8.2 SQLite 相关约束
|
||||
@ -220,8 +243,8 @@ python3 scripts/validate_signal_recency.py
|
||||
|
||||
这个仓库里有不少“迭代中兼容旧逻辑”的痕迹,例如:
|
||||
|
||||
- `config_loader.py` 中的信号别名兼容
|
||||
- `altcoin_db.py` 中大量 `ALTER TABLE` 迁移兜底
|
||||
- `app/config/config_loader.py` 中的信号别名兼容
|
||||
- `app/db/altcoin_db.py` 中大量 `ALTER TABLE` 迁移兜底
|
||||
- 多处 `entry_plan_json` / `detail_json` / `*_context_json`
|
||||
|
||||
因此改动时应优先做增量兼容,而不是假设数据库、配置、旧数据永远是干净新鲜的。
|
||||
@ -231,14 +254,14 @@ python3 scripts/validate_signal_recency.py
|
||||
- 当前目录 **不是 git 仓库根目录**,`git status` 会失败;如果后续需要版本管理,请先确认真正的 Git 根目录或重新初始化。
|
||||
- [DESIGN.md](/Users/aaron/Desktop/code/alphax-docker/DESIGN.md) 当前内容更像一份品牌/样式 YAML,不是这个项目的系统设计文档,阅读时不要误判。
|
||||
- 根目录存在一些临时/非核心文件,例如 `.tmp_patch_tp1.py`、`.tmp_strategy_v2_marker.txt`、`=`,后续开发前建议先确认这些文件是否仍有保留价值。
|
||||
- `web_server.py` 和 `altcoin_db.py` 都已经非常大,后续新增功能应尽量避免继续把复杂度集中到这两个文件。
|
||||
- `app/web/web_server.py` 和 `app/db/altcoin_db.py` 都已经非常大,后续新增功能应尽量避免继续把复杂度集中到这两个文件。
|
||||
|
||||
## 10. 推荐的后续重构方向
|
||||
|
||||
后续若继续开发,建议优先考虑这几个方向:
|
||||
|
||||
1. 把 `web_server.py` 按认证、推荐、策略、管理端拆分路由模块。
|
||||
2. 把 `altcoin_db.py` 拆成 schema/init、recommendation、review、analytics、admin 查询几个子模块。
|
||||
1. 把 `app/web/web_server.py` 按认证、推荐、策略、管理端拆分路由模块。
|
||||
2. 把 `app/db/altcoin_db.py` 拆成 schema/init、recommendation、review、analytics、admin 查询几个子模块。
|
||||
3. 为 `rules.yaml` 建立更明确的 schema 校验,避免配置漂移。
|
||||
4. 给核心脚本增加更稳定的 CLI 参数入口,而不是依赖脚本内默认行为。
|
||||
5. 梳理推送链路,把“是否推送”的判断和“推送内容生成”进一步解耦。
|
||||
|
||||
@ -53,18 +53,18 @@ docker compose up -d alphax-scheduler
|
||||
|
||||
| 任务 | 脚本 | 间隔 |
|
||||
|---|---|---|
|
||||
| 事件舆情 | `event_driven_screener.py --once` | 60s |
|
||||
| 价格跟踪 | `price_tracker.py` | 180s |
|
||||
| 爆发确认 | `altcoin_confirm.py` | 600s |
|
||||
| 粗筛/细筛 | `altcoin_screener.py` | 900s |
|
||||
| 舆情采集 | `sentiment_monitor.py --collect` | 1800s |
|
||||
| 复盘 | `review_engine.py` | 24h |
|
||||
| 事件舆情 | `python -m app.services.event_driven_screener --once` | 60s |
|
||||
| 价格跟踪 | `python -m app.services.price_tracker` | 180s |
|
||||
| 爆发确认 | `python -m app.services.altcoin_confirm` | 600s |
|
||||
| 粗筛/细筛 | `python -m app.services.altcoin_screener` | 900s |
|
||||
| 舆情采集 | `python -m app.services.sentiment_monitor --collect` | 1800s |
|
||||
| 复盘 | `python -m app.services.review_engine` | 24h |
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
cd /home/ubuntu/quant_monitor/alphax-docker
|
||||
python3 -m py_compile altcoin_db.py auth_db.py opportunity_lifecycle.py altcoin_screener.py altcoin_confirm.py price_tracker.py event_driven_screener.py sentiment_monitor.py review_engine.py web_server.py docker/scheduler.py scripts/validate_docker_layout.py
|
||||
python3 -m py_compile app/db/altcoin_db.py app/db/auth_db.py app/core/opportunity_lifecycle.py app/services/altcoin_screener.py app/services/altcoin_confirm.py app/services/price_tracker.py app/services/event_driven_screener.py app/services/sentiment_monitor.py app/services/review_engine.py app/web/web_server.py docker/scheduler.py scripts/validate_docker_layout.py
|
||||
python3 scripts/validate_docker_layout.py
|
||||
python3 scripts/validate_state_machine.py
|
||||
python3 scripts/validate_push_state_flow.py
|
||||
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Application package for AlphaX."""
|
||||
1
app/analysis/__init__.py
Normal file
1
app/analysis/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Analysis modules."""
|
||||
@ -21,10 +21,10 @@ import requests
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from altcoin_db import get_conn, record_missed_explosion, upsert_strategy_rule_candidate
|
||||
from pa_engine import full_pa_analysis, classify_candles, calc_atr
|
||||
from sector_map import get_sector_for_coin, COIN_TO_SECTORS, SECTOR_MEMBERS
|
||||
import config_loader
|
||||
from app.db.altcoin_db import get_conn, record_missed_explosion, upsert_strategy_rule_candidate
|
||||
from app.core.pa_engine import full_pa_analysis, classify_candles, calc_atr
|
||||
from app.core.sector_map import get_sector_for_coin, COIN_TO_SECTORS, SECTOR_MEMBERS
|
||||
from app.config import config_loader
|
||||
|
||||
BINANCE_API = "https://api.binance.com/api/v3"
|
||||
|
||||
@ -576,4 +576,4 @@ def run_reverse_analysis():
|
||||
|
||||
if __name__ == "__main__":
|
||||
results = run_reverse_analysis()
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
1
app/config/__init__.py
Normal file
1
app/config/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Configuration modules."""
|
||||
@ -5,11 +5,13 @@ review_engine 调整权重后直接写回 yaml,下次运行自动生效
|
||||
import copy
|
||||
import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
RULES_PATH = os.path.join(os.path.dirname(__file__), "rules.yaml")
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
RULES_PATH = str(REPO_ROOT / "rules.yaml")
|
||||
|
||||
_cache = None
|
||||
_cache_mtime = None
|
||||
@ -154,7 +156,7 @@ def get_signal_weights():
|
||||
canonical[normalize_signal_name(sig)] = weight
|
||||
|
||||
try:
|
||||
from altcoin_db import get_signal_weights as db_get_weights
|
||||
from app.db.altcoin_db import get_signal_weights as db_get_weights
|
||||
db_weights = db_get_weights()
|
||||
for sig, data in db_weights.items():
|
||||
norm_sig = normalize_signal_name(sig)
|
||||
@ -226,7 +228,7 @@ def update_signal_weight(signal_name, new_weight):
|
||||
rules.setdefault("signal_weights", {})[canonical_name] = new_weight
|
||||
save_rules(rules)
|
||||
try:
|
||||
from altcoin_db import update_signal_performance
|
||||
from app.db.altcoin_db import update_signal_performance
|
||||
update_signal_performance(canonical_name, category=None, is_hit=None, pnl=None, weight_override=new_weight)
|
||||
except Exception:
|
||||
pass
|
||||
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Core strategy and domain modules."""
|
||||
@ -13,7 +13,7 @@ import pandas as pd
|
||||
import numpy as np
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
|
||||
from config_loader import (
|
||||
from app.config.config_loader import (
|
||||
dynamic_k_thresholds,
|
||||
static_k_thresholds,
|
||||
zone_params,
|
||||
@ -781,4 +781,4 @@ def full_pa_analysis(df: pd.DataFrame, timeframe: str = "4h") -> Dict:
|
||||
"ignition_points": ignition_points,
|
||||
"atr": round(atr, 6),
|
||||
"trend_exhaustion": exhaustion,
|
||||
}
|
||||
}
|
||||
1
app/db/__init__.py
Normal file
1
app/db/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Database access modules."""
|
||||
@ -8,9 +8,10 @@ import json
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from config_loader import get_meta, get_screener_section, confirm_state_cooldown_hours
|
||||
from opportunity_lifecycle import (
|
||||
from app.config.config_loader import get_meta, get_screener_section, confirm_state_cooldown_hours
|
||||
from app.core.opportunity_lifecycle import (
|
||||
apply_entry_quality_gate,
|
||||
normalize_json_object,
|
||||
derive_display_bucket,
|
||||
@ -18,7 +19,8 @@ from opportunity_lifecycle import (
|
||||
is_executed_lifecycle,
|
||||
)
|
||||
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
|
||||
|
||||
def get_conn():
|
||||
@ -2177,7 +2179,7 @@ def get_review_stats():
|
||||
conn = get_conn()
|
||||
revision_started_at = ""
|
||||
try:
|
||||
from config_loader import get_meta
|
||||
from app.config.config_loader import get_meta
|
||||
meta = get_meta() or {}
|
||||
revision_started_at = (meta.get("strategy_revision_started_at") or "").strip()
|
||||
except Exception:
|
||||
@ -18,9 +18,11 @@ import smtplib
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
from email.message import EmailMessage
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
SESSION_DAYS = 30
|
||||
VERIFY_CODE_MINUTES = 15
|
||||
FREE_TRIAL_DAYS = 30
|
||||
1
app/integrations/__init__.py
Normal file
1
app/integrations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""External integration modules."""
|
||||
@ -14,7 +14,7 @@ import sys
|
||||
import json
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from feishu_push import push_card
|
||||
from app.integrations.feishu_push import push_card
|
||||
|
||||
CHAT_ID = "oc_2c597ad94167102922de142928e2917a"
|
||||
|
||||
@ -53,7 +53,7 @@ def push_review_report(review_results):
|
||||
|
||||
# Section 2: 信号绩效TOP5
|
||||
# 从review_results中提取信号绩效信息
|
||||
from altcoin_db import get_signal_weights
|
||||
from app.db.altcoin_db import get_signal_weights
|
||||
weights = get_signal_weights()
|
||||
sig_perf_list = sorted(
|
||||
[(sig, data) for sig, data in weights.items() if data.get("total_count", 0) >= 3],
|
||||
@ -295,4 +295,4 @@ if __name__ == "__main__":
|
||||
|
||||
# 测试rule notification
|
||||
ok2, r2 = push_rule_update_notification("rule_20260429_001", "涨幅榜60%有起爆点 → 起爆点是爆发前必现信号")
|
||||
print(f"规律通知: ok={ok2}")
|
||||
print(f"规律通知: ok={ok2}")
|
||||
1
app/services/__init__.py
Normal file
1
app/services/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Service and workflow modules."""
|
||||
@ -22,16 +22,17 @@ import os
|
||||
import time
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from sector_map import get_burst_threshold, is_meme_coin, get_sector_for_coin, COIN_TO_SECTORS
|
||||
from altcoin_db import (
|
||||
from app.core.sector_map import get_burst_threshold, is_meme_coin, get_sector_for_coin, COIN_TO_SECTORS
|
||||
from app.db.altcoin_db import (
|
||||
init_db, expire_old_states, expire_old_recommendations,
|
||||
get_candidates_for_confirm, update_state, get_conn, create_recommendation, log_screening,
|
||||
log_cron_run, should_push, log_push, update_latest_price_cache, get_recommendation_for_push,
|
||||
)
|
||||
from feishu_push import push_recommendation_state_alert
|
||||
from config_loader import (
|
||||
from app.integrations.feishu_push import push_recommendation_state_alert
|
||||
from app.config.config_loader import (
|
||||
get_strategy_direction,
|
||||
vp_fly_params,
|
||||
confirm_min_score,
|
||||
@ -40,15 +41,16 @@ from config_loader import (
|
||||
confirm_stop_loss_params,
|
||||
get_strategy_params,
|
||||
)
|
||||
from opportunity_lifecycle import apply_entry_quality_gate
|
||||
from config_loader import _get_section as _get_cfg_section
|
||||
from pa_engine import (
|
||||
from app.core.opportunity_lifecycle import apply_entry_quality_gate
|
||||
from app.config.config_loader import _get_section as _get_cfg_section
|
||||
from app.core.pa_engine import (
|
||||
classify_candles, calc_atr, find_supply_demand_zones,
|
||||
find_continuous_k, detect_ignition_point, full_pa_analysis,
|
||||
analyze_entry_point, detect_trend_exhaustion,
|
||||
)
|
||||
|
||||
exchange = ccxt.binance({"enableRateLimit": True})
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def fetch_klines(symbol, timeframe, limit=200):
|
||||
@ -66,7 +68,7 @@ def symbol_recently_closed(symbol: str, hours: int = 8) -> bool:
|
||||
用于冷却期:刚止盈的币不宜立即追入。"""
|
||||
import sqlite3, os
|
||||
from datetime import datetime, timezone, timedelta
|
||||
db = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
db = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
conn = sqlite3.connect(db)
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
|
||||
row = conn.execute("""
|
||||
@ -1340,4 +1342,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
@ -23,20 +23,21 @@ import os
|
||||
import time
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from sector_map import (
|
||||
from app.core.sector_map import (
|
||||
SECTOR_MEMBERS, COIN_TO_SECTORS, MEME_SECTORS,
|
||||
MIN_24H_VOLUME_USD, MEME_MIN_24H_VOLUME_USD,
|
||||
get_sector_for_coin, is_meme_coin, get_burst_threshold,
|
||||
dynamic_leader_detection,
|
||||
)
|
||||
from altcoin_db import (
|
||||
from app.db.altcoin_db import (
|
||||
init_db, expire_old_states, update_state, get_candidates_for_confirm,
|
||||
log_screening, create_recommendation, expire_old_recommendations,
|
||||
log_cron_run,
|
||||
)
|
||||
from config_loader import (
|
||||
from app.config.config_loader import (
|
||||
get_signal_weights,
|
||||
get_strategy_direction,
|
||||
get_meta,
|
||||
@ -48,12 +49,13 @@ from config_loader import (
|
||||
get_screener_section,
|
||||
sentiment_max_bonus,
|
||||
)
|
||||
from pa_engine import (
|
||||
from app.core.pa_engine import (
|
||||
classify_candles, calc_atr, find_supply_demand_zones,
|
||||
find_continuous_k, detect_ignition_point, full_pa_analysis,
|
||||
)
|
||||
|
||||
exchange = ccxt.binance({"enableRateLimit": True})
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
# ==================== 排除列表 ====================
|
||||
STABLECOINS = {
|
||||
@ -574,7 +576,7 @@ def layer1_coarse_filter():
|
||||
# 过去24h内在screening_log出现过的币,不受"涨太多"过滤限制
|
||||
# 防止ICP/SUI类:系统早已盯上但被burst_threshold×1.5误挡
|
||||
import sqlite3 as _sq
|
||||
_c = _sq.connect(os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db")))
|
||||
_c = _sq.connect(os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db")))
|
||||
_recent = _c.execute("""
|
||||
SELECT DISTINCT symbol FROM screening_log
|
||||
WHERE scan_time >= datetime('now', '-24 hours')
|
||||
@ -859,7 +861,7 @@ def layer1_coarse_filter():
|
||||
|
||||
# === 舆情共振加权 ===
|
||||
try:
|
||||
from sentiment_monitor import get_sentiment_scores
|
||||
from app.services.sentiment_monitor import get_sentiment_scores
|
||||
sentiment_cfg = get_screener_section("sentiment") or {}
|
||||
if sentiment_cfg.get("enabled", True):
|
||||
sentiment_scores = get_sentiment_scores()
|
||||
@ -14,6 +14,7 @@ import hashlib
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from email.utils import parsedate_to_datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import ccxt
|
||||
@ -23,9 +24,9 @@ import yaml
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from config_loader import load_rules, get_meta, get_strategy_direction
|
||||
from altcoin_db import init_db, get_conn, create_recommendation, log_screening, log_cron_run, should_push, log_push, get_recommendation_for_push
|
||||
from altcoin_screener import (
|
||||
from app.config.config_loader import load_rules, get_meta, get_strategy_direction
|
||||
from app.db.altcoin_db import init_db, get_conn, create_recommendation, log_screening, log_cron_run, should_push, log_push, get_recommendation_for_push
|
||||
from app.services.altcoin_screener import (
|
||||
fetch_all_tickers,
|
||||
detect_volume_price_fly,
|
||||
detect_static_accumulation,
|
||||
@ -37,11 +38,12 @@ from altcoin_screener import (
|
||||
EXCLUDED_BASES,
|
||||
EXCLUDED_BASE_SUFFIXES,
|
||||
)
|
||||
from altcoin_confirm import fetch_derivatives_context
|
||||
from pa_engine import full_pa_analysis, calc_atr
|
||||
from feishu_push import push_recommendation_state_alert
|
||||
from app.services.altcoin_confirm import fetch_derivatives_context
|
||||
from app.core.pa_engine import full_pa_analysis, calc_atr
|
||||
from app.integrations.feishu_push import push_recommendation_state_alert
|
||||
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
exchange = ccxt.binance({"enableRateLimit": True})
|
||||
|
||||
LEVEL_RANK = {"S": 4, "A": 3, "B": 2, "C": 1, "D": 0, "RISK": 5}
|
||||
@ -308,7 +310,7 @@ def fetch_coingecko_trending_events():
|
||||
if not cfg.get("enabled", True):
|
||||
return []
|
||||
try:
|
||||
from sentiment_monitor import fetch_trending_coins, _get_previous_trending
|
||||
from app.services.sentiment_monitor import fetch_trending_coins, _get_previous_trending
|
||||
trending = fetch_trending_coins()
|
||||
prev = {r["symbol"] for r in _get_previous_trending()}
|
||||
events = []
|
||||
@ -21,23 +21,25 @@ import json
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from altcoin_db import (
|
||||
from app.db.altcoin_db import (
|
||||
init_db, get_active_recommendations, update_recommendation_tracking,
|
||||
expire_old_recommendations, get_stats, update_recommendation_action_status,
|
||||
should_push, log_push, apply_recommendation_state_transition, log_cron_run,
|
||||
update_latest_price_cache,
|
||||
)
|
||||
from pa_engine import (
|
||||
from app.core.pa_engine import (
|
||||
calc_atr, full_pa_analysis, detect_trend_exhaustion,
|
||||
analyze_entry_point,
|
||||
)
|
||||
from feishu_push import push_altcoin_tp_sl_alert
|
||||
from config_loader import load_rules
|
||||
from opportunity_lifecycle import apply_entry_quality_gate
|
||||
from app.integrations.feishu_push import push_altcoin_tp_sl_alert
|
||||
from app.config.config_loader import load_rules
|
||||
from app.core.opportunity_lifecycle import apply_entry_quality_gate
|
||||
|
||||
exchange = ccxt.binance({"enableRateLimit": True})
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def fetch_klines(symbol, timeframe, limit=200):
|
||||
@ -316,7 +318,7 @@ def track_prices():
|
||||
if abs(trail_level - old_trail) > 0.000001:
|
||||
entry_plan["trailing_stop_level"] = trail_level
|
||||
import sqlite3 as _sq
|
||||
_c2 = _sq.connect(os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db")))
|
||||
_c2 = _sq.connect(os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db")))
|
||||
_c2.execute("UPDATE recommendation SET entry_plan_json=? WHERE id=?",
|
||||
(json.dumps(entry_plan, ensure_ascii=False), rec["id"]))
|
||||
_c2.commit()
|
||||
@ -16,21 +16,21 @@ from datetime import datetime, timedelta
|
||||
from collections import defaultdict, Counter
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from altcoin_db import (
|
||||
from app.db.altcoin_db import (
|
||||
get_conn, record_review, update_signal_performance,
|
||||
get_signal_weights, record_missed_explosion, get_review_stats, log_strategy_iteration,
|
||||
upsert_strategy_rule_candidate, record_strategy_failure_pattern,
|
||||
get_strategy_rule_candidates, update_strategy_rule_candidate_status,
|
||||
refresh_strategy_candidate_performance,
|
||||
)
|
||||
from pa_engine import classify_candles, calc_atr, full_pa_analysis
|
||||
from config_loader import (
|
||||
from app.core.pa_engine import classify_candles, calc_atr, full_pa_analysis
|
||||
from app.config.config_loader import (
|
||||
get_review_params, update_meta, get_learned_rules, add_learned_rule,
|
||||
get_rules_snapshot, diff_rule_snapshots, get_meta, update_signal_weight,
|
||||
promote_candidate_rule_to_learned_rule, bump_strategy_patch_version,
|
||||
)
|
||||
import reverse_analysis
|
||||
import feishu_review_push
|
||||
from app.analysis import reverse_analysis
|
||||
from app.integrations import feishu_review_push
|
||||
import requests
|
||||
|
||||
BINANCE_API = "https://api.binance.com/api/v3"
|
||||
@ -1340,7 +1340,7 @@ def run_review():
|
||||
# 通过config_loader更新迭代计数
|
||||
current_meta = {}
|
||||
try:
|
||||
from config_loader import get_meta
|
||||
from app.config.config_loader import get_meta
|
||||
current_meta = get_meta()
|
||||
except:
|
||||
pass
|
||||
@ -1387,4 +1387,4 @@ def run_review():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_review()
|
||||
run_review()
|
||||
@ -13,12 +13,14 @@ import requests
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime, timedelta
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
COINGECKO_TRENDING_URL = "https://api.coingecko.com/api/v3/search/trending"
|
||||
GOOGLE_NEWS_RSS = "https://news.google.com/rss/search?q={query}&hl=en-US&gl=US&ceid=US:en"
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
|
||||
|
||||
def _get_conn():
|
||||
1
app/web/__init__.py
Normal file
1
app/web/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Web application modules."""
|
||||
@ -9,15 +9,14 @@ import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from contextvars import ContextVar
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI, HTTPException, Cookie, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pydantic import BaseModel
|
||||
|
||||
import auth_db
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from altcoin_db import (
|
||||
from app.db import auth_db
|
||||
from app.db.altcoin_db import (
|
||||
init_db, get_active_recommendations, get_active_recommendations_deduped, get_all_recommendations,
|
||||
get_screening_history, get_stats, get_review_stats, get_cron_run_logs, get_cron_run_summary,
|
||||
get_conn, _derive_execution_fields, get_strategy_insights, get_strategy_rule_candidates,
|
||||
@ -25,10 +24,12 @@ from altcoin_db import (
|
||||
dry_run_strategy_candidate_performance, backfill_strategy_failure_patterns,
|
||||
generate_candidates_from_review_history,
|
||||
)
|
||||
from config_loader import get_signal_weights, get_meta
|
||||
from app.config.config_loader import get_signal_weights, get_meta
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
app = FastAPI(title="山寨币爆发监控 v11")
|
||||
templates = Jinja2Templates(directory="static")
|
||||
templates = Jinja2Templates(directory=str(REPO_ROOT / "static"))
|
||||
_current_request = ContextVar("current_request", default=None)
|
||||
|
||||
|
||||
@ -537,7 +538,7 @@ async def api_cron_summary(hours: int = 24, altcoin_session: str = Cookie(defaul
|
||||
async def api_sentiment(hours: int = 6, altcoin_session: str = Cookie(default="")):
|
||||
"""返回舆情监控数据:以消息/事件为主,币种作为关联信息。"""
|
||||
_require_api_user_with_subscription(altcoin_session)
|
||||
db = os.getenv("ALPHAX_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "altcoin_monitor.db"))
|
||||
db = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
conn = sqlite3.connect(db)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
@ -777,7 +778,7 @@ async def api_kline(symbol: str, interval: str = "1d", limit: int = 60, altcoin_
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index():
|
||||
"""落地页 — 原始 HTML,无 Jinja2"""
|
||||
landing_path = os.path.join(os.path.dirname(__file__), "static", "index.html")
|
||||
landing_path = str(REPO_ROOT / "static" / "index.html")
|
||||
with open(landing_path, "r", encoding="utf-8") as f:
|
||||
return HTMLResponse(content=f.read())
|
||||
|
||||
@ -785,7 +786,7 @@ async def index():
|
||||
@app.get("/auth", response_class=HTMLResponse)
|
||||
async def auth_page():
|
||||
"""登录/注册页 — 原始 HTML,无 Jinja2"""
|
||||
auth_path = os.path.join(os.path.dirname(__file__), "static", "auth.html")
|
||||
auth_path = str(REPO_ROOT / "static" / "auth.html")
|
||||
with open(auth_path, "r", encoding="utf-8") as f:
|
||||
return HTMLResponse(content=f.read())
|
||||
|
||||
@ -1008,7 +1009,11 @@ async def api_strategy_candidates_generate_history(dry_run: bool = False):
|
||||
return result
|
||||
|
||||
|
||||
STOCK_REPORT_TEMPLATE = open(os.path.join(os.path.dirname(__file__), "stock_report_template.html"), "r", encoding="utf-8").read()
|
||||
STOCK_REPORT_TEMPLATE = open(
|
||||
REPO_ROOT / "templates" / "stock_report_template.html",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
).read()
|
||||
|
||||
HTML_PAGE = r'''<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
@ -4247,4 +4252,4 @@ async def api_admin_users(search: str = "", offset: int = 0, limit: int = 50, ta
|
||||
async def api_admin_orders(search: str = "", offset: int = 0, limit: int = 50, status: str = "all",
|
||||
altcoin_session: str = Cookie(default="")):
|
||||
_require_admin(altcoin_session)
|
||||
return auth_db.get_admin_orders(search=search, offset=offset, limit=limit, status=status)
|
||||
return auth_db.get_admin_orders(search=search, offset=offset, limit=limit, status=status)
|
||||
1079
backtest_result.json
1079
backtest_result.json
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ fi
|
||||
|
||||
case "${1:-web}" in
|
||||
web)
|
||||
exec python -m uvicorn web_server:app --host 0.0.0.0 --port "${PORT:-8190}"
|
||||
exec python -m uvicorn app.web.web_server:app --host 0.0.0.0 --port "${PORT:-8190}"
|
||||
;;
|
||||
scheduler)
|
||||
exec python /app/docker/scheduler.py
|
||||
|
||||
@ -25,7 +25,7 @@ DRY_RUN = os.getenv("ALPHAX_SCHEDULER_DRY_RUN", "1").strip() not in {"0", "false
|
||||
@dataclass
|
||||
class Job:
|
||||
name: str
|
||||
script: str
|
||||
module: str
|
||||
every_seconds: int
|
||||
args: tuple[str, ...] = ()
|
||||
initial_delay: int = 0
|
||||
@ -43,7 +43,7 @@ def env_for_child() -> dict[str, str]:
|
||||
|
||||
|
||||
def run_job(job: Job) -> None:
|
||||
cmd = [PYTHON, str(ROOT / job.script), *job.args]
|
||||
cmd = [PYTHON, "-m", job.module, *job.args]
|
||||
print(f"[{now_str()}] [scheduler] start {job.name}: {' '.join(cmd)}", flush=True)
|
||||
if DRY_RUN:
|
||||
print(f"[{now_str()}] [scheduler] DRY_RUN=1 skip {job.name}", flush=True)
|
||||
@ -75,12 +75,12 @@ def run_job(job: Job) -> None:
|
||||
def build_jobs() -> list[Job]:
|
||||
# 与当前宿主机 crontab 对齐,但串行执行。
|
||||
return [
|
||||
Job("event", "event_driven_screener.py", 60, ("--once",), initial_delay=5),
|
||||
Job("tracker", "price_tracker.py", 180, initial_delay=20),
|
||||
Job("confirm", "altcoin_confirm.py", 600, initial_delay=40),
|
||||
Job("screener", "altcoin_screener.py", 900, initial_delay=80),
|
||||
Job("sentiment", "sentiment_monitor.py", 1800, ("--collect",), initial_delay=120),
|
||||
Job("review", "review_engine.py", 24 * 3600, initial_delay=300),
|
||||
Job("event", "app.services.event_driven_screener", 60, ("--once",), initial_delay=5),
|
||||
Job("tracker", "app.services.price_tracker", 180, initial_delay=20),
|
||||
Job("confirm", "app.services.altcoin_confirm", 600, initial_delay=40),
|
||||
Job("screener", "app.services.altcoin_screener", 900, initial_delay=80),
|
||||
Job("sentiment", "app.services.sentiment_monitor", 1800, ("--collect",), initial_delay=120),
|
||||
Job("review", "app.services.review_engine", 24 * 3600, initial_delay=300),
|
||||
]
|
||||
|
||||
|
||||
|
||||
122
docs/PROJECT_STRUCTURE.md
Normal file
122
docs/PROJECT_STRUCTURE.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Project Structure
|
||||
|
||||
## 目标
|
||||
|
||||
这次整理的目标不是把项目“重写成新架构”,而是先把目录语义拉清楚:
|
||||
|
||||
- 根目录只放主运行链路和顶层配置
|
||||
- 工具脚本集中到 `tools/`
|
||||
- 校验脚本集中到 `scripts/`
|
||||
- 模板资源集中到 `templates/`
|
||||
- 运行/分析产物集中到 `reports/`
|
||||
- 历史遗留与备份集中到 `legacy/`
|
||||
|
||||
## 当前建议心智模型
|
||||
|
||||
### 1. 真实实现层
|
||||
|
||||
真实实现现在集中在 `app/` 下:
|
||||
|
||||
- `app/services/`
|
||||
- `app/db/`
|
||||
- `app/core/`
|
||||
- `app/config/`
|
||||
- `app/integrations/`
|
||||
- `app/analysis/`
|
||||
- `app/web/`
|
||||
|
||||
### 2. 运行主链路
|
||||
|
||||
- `app/services/altcoin_screener.py`
|
||||
- `app/services/altcoin_confirm.py`
|
||||
- `app/services/price_tracker.py`
|
||||
- `app/services/event_driven_screener.py`
|
||||
- `app/services/review_engine.py`
|
||||
- `app/web/web_server.py`
|
||||
|
||||
### 3. 核心共享层
|
||||
|
||||
- `app/db/altcoin_db.py`
|
||||
- `app/db/auth_db.py`
|
||||
- `app/config/config_loader.py`
|
||||
- `app/core/opportunity_lifecycle.py`
|
||||
- `app/core/pa_engine.py`
|
||||
- `app/core/sector_map.py`
|
||||
- `app/integrations/feishu_push.py`
|
||||
- `app/integrations/feishu_review_push.py`
|
||||
|
||||
### 4. 配置与部署
|
||||
|
||||
- `rules.yaml`
|
||||
- `.env.example`
|
||||
- `Dockerfile`
|
||||
- `docker-compose.yml`
|
||||
- `docker/`
|
||||
|
||||
### 5. 前端与模板
|
||||
|
||||
- `static/`
|
||||
- `templates/`
|
||||
|
||||
### 6. 开发辅助
|
||||
|
||||
- `tests/`
|
||||
- `scripts/`
|
||||
- `tools/`
|
||||
- `docs/`
|
||||
|
||||
### 7. 非主路径归档
|
||||
|
||||
- `legacy/`
|
||||
- `reports/`
|
||||
|
||||
## 已整理的文件
|
||||
|
||||
### 移入 `tools/`
|
||||
|
||||
- `backtest.py` -> `tools/backtest.py`
|
||||
- `extract_summary.py` -> `tools/extract_summary.py`
|
||||
- `summarize_output.py` -> `tools/summarize_output.py`
|
||||
|
||||
### 移入 `scripts/`
|
||||
|
||||
- `validate_params.py` -> `scripts/validate_params.py`
|
||||
|
||||
### 移入 `templates/`
|
||||
|
||||
- `stock_report_template.html` -> `templates/stock_report_template.html`
|
||||
|
||||
### 移入 `reports/`
|
||||
|
||||
- `backtest_result.json` -> `reports/backtest_result.json`
|
||||
|
||||
### 移入 `docs/reference/`
|
||||
|
||||
- `schema.py` -> `docs/reference/schema_reference.py`
|
||||
|
||||
### 移入 `legacy/`
|
||||
|
||||
- `coin_state_tracker.py`
|
||||
- `price_tracker_ws.py`
|
||||
- `legacy/web/index.html`
|
||||
- `legacy/static/app.html.bak`
|
||||
- `legacy/scratch/*`
|
||||
|
||||
## 后续建议
|
||||
|
||||
如果继续整理,建议下一步做实现层拆分,而不是再搬目录:
|
||||
|
||||
1. 拆 `web_server.py`
|
||||
2. 拆 `altcoin_db.py`
|
||||
3. 为 `rules.yaml` 增加 schema 校验
|
||||
4. 为主脚本建立统一 CLI 入口
|
||||
|
||||
## 当前结论
|
||||
|
||||
这次整理后:
|
||||
|
||||
- `app/` 承载真实实现
|
||||
- 根目录不再堆放业务实现脚本
|
||||
- 旧工具/旧资产/产物不再继续污染主目录
|
||||
|
||||
这不是最终态,但已经从“所有实现都摊在根目录”进到了“实现按职责分层”的可维护阶段。
|
||||
@ -11,9 +11,12 @@
|
||||
此文件不应被 screener/confirm 等模块直接导入调用。
|
||||
"""
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
DB_PATH = "/home/ubuntu/quant_monitor/altcoin/altcoin_monitor.db"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DB_PATH = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
|
||||
|
||||
def init_db():
|
||||
@ -113,4 +116,4 @@ def init_db():
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
print("DB Schema初始化完成(4张表+5个索引)")
|
||||
print("DB Schema初始化完成(4张表+5个索引)")
|
||||
@ -13,7 +13,7 @@ from datetime import datetime
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from altcoin_db import (
|
||||
from app.db.altcoin_db import (
|
||||
init_db,
|
||||
get_active_recommendations_deduped,
|
||||
update_recommendation_tracking,
|
||||
@ -21,7 +21,7 @@ from altcoin_db import (
|
||||
should_push,
|
||||
log_push,
|
||||
)
|
||||
from feishu_push import push_altcoin_tp_sl_alert
|
||||
from app.integrations.feishu_push import push_altcoin_tp_sl_alert
|
||||
|
||||
POLL_INTERVAL = 5
|
||||
REFRESH_INTERVAL = 60
|
||||
@ -986,8 +986,8 @@ monitoring:
|
||||
min_score_max: 6
|
||||
require_human_if_exceeded: true
|
||||
param_audit:
|
||||
description: 参数变更审计:每次复盘运行validate_params.py验证关键参数未被意外修改
|
||||
validate_script: validate_params.py
|
||||
description: 参数变更审计:每次复盘运行scripts/validate_params.py验证关键参数未被意外修改
|
||||
validate_script: scripts/validate_params.py
|
||||
hash_algorithm: semantic_sha256
|
||||
critical_sections:
|
||||
- confirm
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""参数变更审计:检测 rules.yaml 是否被意外修改"""
|
||||
import hashlib, yaml, sys
|
||||
import hashlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RULES_PATH = Path(__file__).parent / 'rules.yaml'
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
RULES_PATH = REPO_ROOT / "rules.yaml"
|
||||
|
||||
def compute_semantic_hash(rules_dict):
|
||||
"""语义哈希:忽略格式差异,只对关键参数字段做哈希"""
|
||||
@ -8,8 +8,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -340,4 +340,3 @@ def test_version_filter_labels_use_plan_not_executable_to_avoid_wait_pullback_co
|
||||
assert '历史推荐' in html
|
||||
# v1.7.7: 新看板版本下拉用 (${v.count}) 格式 + 默认选中最新版本
|
||||
assert 'v.count' in html
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from altcoin_confirm import _is_candidate_fresh, _event_time_from_age
|
||||
from app.services.altcoin_confirm import _is_candidate_fresh, _event_time_from_age
|
||||
|
||||
|
||||
def test_candidate_fresh_when_state_detected_recently_without_current_trigger():
|
||||
|
||||
@ -8,8 +8,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -8,7 +8,7 @@ import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
import event_driven_screener as ed
|
||||
from app.services import event_driven_screener as ed
|
||||
|
||||
|
||||
def test_symbol_extraction_filters_usdt_suffix_and_pollution():
|
||||
|
||||
@ -8,8 +8,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
from test_actionable_active_recommendations import _insert_recommendation
|
||||
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ def make_df(highs, lows, closes, opens=None, volumes=None):
|
||||
|
||||
def test_detect_higher_lows_finds_clear_pattern():
|
||||
"""验证底部抬高检测能识别清晰的逐步抬高低点"""
|
||||
from altcoin_screener import detect_higher_lows
|
||||
from app.services.altcoin_screener import detect_higher_lows
|
||||
|
||||
lows, highs, closes, volumes = [], [], [], []
|
||||
for seg in range(6):
|
||||
@ -44,7 +44,7 @@ def test_detect_higher_lows_finds_clear_pattern():
|
||||
|
||||
def test_detect_higher_lows_rejects_declining_trend():
|
||||
"""验证下降趋势不会被误判为底部抬高"""
|
||||
from altcoin_screener import detect_higher_lows
|
||||
from app.services.altcoin_screener import detect_higher_lows
|
||||
|
||||
lows, highs, closes, volumes = [], [], [], []
|
||||
for seg in range(6):
|
||||
@ -64,7 +64,7 @@ def test_detect_higher_lows_rejects_declining_trend():
|
||||
|
||||
def test_detect_compression_surge_detects_tight_then_volume():
|
||||
"""验证压缩后放量模式能被检测"""
|
||||
from altcoin_screener import detect_compression_surge
|
||||
from app.services.altcoin_screener import detect_compression_surge
|
||||
|
||||
lows = [100 + np.random.uniform(-0.5, 0.5) for _ in range(24)]
|
||||
highs = [l + np.random.uniform(1, 2.5) for l in lows]
|
||||
@ -82,7 +82,7 @@ def test_detect_compression_surge_detects_tight_then_volume():
|
||||
|
||||
def test_detect_compression_surge_rejects_wide_range():
|
||||
"""验证宽幅震荡不触发压缩检测"""
|
||||
from altcoin_screener import detect_compression_surge
|
||||
from app.services.altcoin_screener import detect_compression_surge
|
||||
|
||||
lows = [np.random.uniform(80, 120) for _ in range(24)]
|
||||
highs = [l + np.random.uniform(5, 15) for l in lows]
|
||||
@ -97,7 +97,7 @@ def test_detect_compression_surge_rejects_wide_range():
|
||||
|
||||
def test_signal_deprecation_config_exists():
|
||||
"""验证信号淘汰机制配置可正确读取"""
|
||||
from config_loader import get_review_params
|
||||
from app.config.config_loader import get_review_params
|
||||
|
||||
dep = get_review_params().get("signal_deprecation", {})
|
||||
assert dep.get("enabled") is True
|
||||
|
||||
@ -10,7 +10,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
class RecommendationHistoryBase(unittest.TestCase):
|
||||
|
||||
@ -9,8 +9,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -9,8 +9,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
||||
@ -4,8 +4,8 @@ import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from opportunity_lifecycle import apply_entry_quality_gate
|
||||
import price_tracker_ws
|
||||
from app.core.opportunity_lifecycle import apply_entry_quality_gate
|
||||
from legacy import price_tracker_ws
|
||||
|
||||
|
||||
def test_risk_reward_false_blocks_buy_now():
|
||||
|
||||
@ -5,7 +5,7 @@ import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from pa_engine import calc_atr, classify_candles, detect_ignition_point
|
||||
from app.core.pa_engine import calc_atr, classify_candles, detect_ignition_point
|
||||
|
||||
|
||||
def _ignition_df(stale_age_bars=6):
|
||||
|
||||
@ -8,8 +8,8 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import auth_db
|
||||
import altcoin_db
|
||||
from app.db import auth_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
class PersonalizationAndStrategyInsightTests(unittest.TestCase):
|
||||
|
||||
@ -5,7 +5,7 @@ import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
class RecommendationExecutionStatusTests(unittest.TestCase):
|
||||
|
||||
@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
class RecommendationStateMainlineTests(unittest.TestCase):
|
||||
|
||||
@ -5,7 +5,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_screener
|
||||
from app.services import altcoin_screener
|
||||
|
||||
|
||||
def test_replay_samples_cover_pnt_cream_ai():
|
||||
|
||||
@ -7,7 +7,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import web_server
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
def test_index_hides_screening_from_top_level_tabs():
|
||||
|
||||
@ -5,7 +5,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
def test_review_stats_contains_strategy_version_summary_and_changelog(monkeypatch):
|
||||
|
||||
@ -7,7 +7,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_screener
|
||||
from app.services import altcoin_screener
|
||||
|
||||
|
||||
def test_fetch_all_tickers_filters_stable_and_fiat_suffixes(monkeypatch):
|
||||
|
||||
@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
class RecommendationSignalTrustTests(unittest.TestCase):
|
||||
|
||||
@ -9,8 +9,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import web_server
|
||||
from app.db import altcoin_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -8,9 +8,9 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import review_engine
|
||||
import config_loader
|
||||
from app.db import altcoin_db
|
||||
from app.services import review_engine
|
||||
from app.config import config_loader
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -6,7 +6,7 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
from app.db import altcoin_db
|
||||
|
||||
|
||||
def test_strategy_version_from_meta():
|
||||
|
||||
@ -5,8 +5,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import altcoin_db
|
||||
import price_tracker_ws
|
||||
from app.db import altcoin_db
|
||||
from legacy import price_tracker_ws
|
||||
|
||||
|
||||
def test_terminal_recommendation_action_status_cannot_be_overwritten_by_entry_signal(monkeypatch, tmp_path):
|
||||
|
||||
@ -9,8 +9,8 @@ PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if PROJECT_DIR not in sys.path:
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
|
||||
import auth_db
|
||||
import web_server
|
||||
from app.db import auth_db
|
||||
from app.web import web_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -5,8 +5,8 @@ import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from altcoin_screener import detect_volume_price_fly
|
||||
from altcoin_confirm import detect_volume_price_fly_1h
|
||||
from app.services.altcoin_screener import detect_volume_price_fly
|
||||
from app.services.altcoin_confirm import detect_volume_price_fly_1h
|
||||
|
||||
|
||||
def _sample_df(stale_age_hours=9):
|
||||
|
||||
@ -2,17 +2,22 @@
|
||||
山寨币策略回测脚本
|
||||
对 DB 中所有有完整入场方案(stop_loss/tp1/tp2)的推荐做模拟跟踪。
|
||||
"""
|
||||
import sys, os, json, sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
sys.path.insert(0, '/home/ubuntu/quant_monitor/altcoin')
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import ccxt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
exchange = ccxt.binance({'enableRateLimit': True})
|
||||
DB = '/home/ubuntu/quant_monitor/altcoin/altcoin_monitor.db'
|
||||
DB = os.getenv("ALPHAX_DB_PATH", str(REPO_ROOT / "data" / "altcoin_monitor.db"))
|
||||
OUTPUT_PATH = REPO_ROOT / "reports" / "backtest_result.json"
|
||||
|
||||
|
||||
def fetch_klines_since(symbol, timeframe, since_ms, limit=500):
|
||||
@ -51,7 +56,6 @@ def simulate_trade(rec, klines_df):
|
||||
close = float(row['close'])
|
||||
ts = row['timestamp']
|
||||
|
||||
current_pnl = (close / entry_price - 1) * 100
|
||||
max_profit_pct = max(max_profit_pct, (high / entry_price - 1) * 100)
|
||||
max_loss_pct = min(max_loss_pct, (low / entry_price - 1) * 100)
|
||||
|
||||
@ -179,7 +183,8 @@ def main():
|
||||
f"{r['pnl_pct']:>6.1f}% {r['max_profit_pct']:>6.1f}% {r['max_loss_pct']:>6.1f}% {r['hours']:>5.1f}")
|
||||
|
||||
# Save to JSON for HTML report
|
||||
with open('/home/ubuntu/quant_monitor/altcoin/backtest_result.json', 'w') as f:
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(OUTPUT_PATH, 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
'generated_at': datetime.now().isoformat(),
|
||||
'total': len(results),
|
||||
@ -193,7 +198,7 @@ def main():
|
||||
'details': [{k: str(v) if isinstance(v, (datetime, pd.Timestamp)) else v
|
||||
for k, v in r.items()} for r in results],
|
||||
}, f, ensure_ascii=False, indent=2, default=str)
|
||||
print(f"\n结果已保存: backtest_result.json")
|
||||
print(f"\n结果已保存: {OUTPUT_PATH}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Loading…
Reference in New Issue
Block a user