This commit is contained in:
aaron 2026-05-13 22:49:47 +08:00
parent d46f3e9801
commit c9f9d1b2a0
67 changed files with 370 additions and 1262 deletions

2
.gitignore vendored
View File

@ -64,3 +64,5 @@ dist/
archive/
backups/
tmp/
legacy/scratch/
reports/*.json

0
=
View File

105
AGENTS.md
View File

@ -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. 梳理推送链路,把“是否推送”的判断和“推送内容生成”进一步解耦。

View File

@ -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
View File

@ -0,0 +1 @@
"""Application package for AlphaX."""

1
app/analysis/__init__.py Normal file
View File

@ -0,0 +1 @@
"""Analysis modules."""

View File

@ -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
View File

@ -0,0 +1 @@
"""Configuration modules."""

View File

@ -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
View File

@ -0,0 +1 @@
"""Core strategy and domain modules."""

View File

@ -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
View File

@ -0,0 +1 @@
"""Database access modules."""

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1 @@
"""External integration modules."""

View File

@ -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
View File

@ -0,0 +1 @@
"""Service and workflow modules."""

View File

@ -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()

View File

@ -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()

View File

@ -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 = []

View File

@ -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()

View File

@ -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()

View File

@ -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
View File

@ -0,0 +1 @@
"""Web application modules."""

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
View 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/` 承载真实实现
- 根目录不再堆放业务实现脚本
- 旧工具/旧资产/产物不再继续污染主目录
这不是最终态,但已经从“所有实现都摊在根目录”进到了“实现按职责分层”的可维护阶段。

View File

@ -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个索引")

View File

@ -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

View File

@ -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

View File

@ -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):
"""语义哈希:忽略格式差异,只对关键参数字段做哈希"""

View File

@ -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

View File

@ -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():

View File

@ -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

View File

@ -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():

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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():

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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():

View File

@ -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():

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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():

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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__':