Author SHA1 Message Date
jackyu66gitandCursor 7f393b93ed refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 01:05:12 +08:00
jackyu66gitandCursor 5c10e35b76 refactor(web): 移除主图威科夫选项与叠层
去掉区间/阶段/时间/VP 开关、Cycle 摘要面板及绘制逻辑;分析请求默认 include_wyckoff=0。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 01:27:51 +08:00
jackyu66gitandCursor 8c165f11cd fix(web): 未完成笔/线段终点对齐图表最新 K 线
各周期使用对应 kline 数据,终点时间 snap 到 candles,优先使用分析 end_price。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 00:40:41 +08:00
jackyu66gitandCursor 97e77847d0 fix(web): 开关缠论元素保留视窗;分周期 Trend 涨跌配色
本地重绘统一冻结视窗;次/次次周期 Trend 上涨下跌使用独立颜色。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 23:56:55 +08:00
jackyu66gitandCursor 90499533fb fix(web): 分析/自动刷新后保留 K 线视窗位置
拆分手动分析与自动刷新拉数路径;全量重建用 logical 优先恢复视窗,
增量 recent 用 scroll+barDelta;避免 barSpacing 重锚与重复冻结导致往右跳。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 23:38:20 +08:00
jackyu66gitandCursor 8ee11317d3 fix(web): 自动刷新保留 K 线视窗;威科夫与图表增量更新
自动刷新改用 tail update 与 scrollToPosition 恢复视窗,避免 setData 后跳到最右;拆分 chart_tv 模块并扩展 analyze/recent API。同步威科夫分析、pipeline 增量构建及相关策略与配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 22:57:43 +08:00
330 changed files with 145957 additions and 33560 deletions
+8 -32
View File
@@ -1,46 +1,22 @@
# MacOS
.DS_Store
# Python编译文件和缓存
# Python
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
.pytest_cache/
# 策略文件的缓存
strategies/__pycache__/
# Machine Learning / AI model files
*_model*_xgb_model.json
*modelchan*.json
*.libsvm
feature_meta
*_model_feature_data.csv
*.pem
# Log files
# Logs & databases
*.log
# Database files
*.sqlite
*.sqlite-shm
*.sqlite-wal
.DS_Store
交易记录/~$交易规则.docx
/datasvc/data
.DS_Store
.DS_Store
/data_provider/data
.DS_Store
.DS_Store
.DS_Store
.DS_Store
.DS_Store
.DS_Store
data_provider/._config.json
.gstack/
# ESS gate / engineering-loop working dirs(归档进 docs/runs/
.gates/
loop/
# Local data
data/
# Local tooling
.gstack/
-33
View File
@@ -1,33 +0,0 @@
# chan — Agent Entry
本仓受 ESS 约束。不要一上来扫全库或加载全部 governance。
## Boot
1. `docs/PROJECT_PROFILE.md`
2. `docs/PROJECT_RULES.md`
3. `docs/STATE/CURRENT.md` + `docs/AGENT_MEMORY.md`
4. 有进行中任务再读 `docs/TASKS/` / 对应 ECR / HANDOFF
5. 角色文件:ESS 根目录 `agents/{ARCHITECT|ENGINEER|REVIEWER|RELEASE_MANAGER}.md`
## Roles(选一)
| 意图 | 角色 |
|------|------|
| 规格 / 架构 / ECR | ARCHITECT |
| 实现 / 修 bug | ENGINEER |
| 审阅 | REVIEWER |
| 发版 / tag | RELEASE_MANAGER |
## Never
- 无 ECR 改 `config/` / `strategies/` 交易逻辑
- 无 ADR 改缠论算法语义
- 无 ECR 删减 `/api/analyze` 字段
- 把聊天记录当成完成;阶段结束须落盘 `docs/`
## Pointers
- TRACEABILITY: `docs/TRACEABILITY.md`
- CHANGELOG: `docs/CHANGELOG/CHANGELOG.md`
- 人类向导:`CLAUDE.md`
-116
View File
@@ -1,116 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
缠论 (Chan Theory) technical analysis system for Freqtrade. Implements Chan Zhong Shui Chan's theory for crypto/stock trading, including fractal (分型), stroke (笔), segment (线段), pivot/center (中枢), and buy/sell point (买卖点) detection.
## Governance
- Agent 入口:`AGENTS.md`boot 顺序)· `docs/PROJECT_PROFILE.md` · `docs/AGENT_MEMORY.md` · `docs/STATE/CURRENT.md`
- ESS 文档:`docs/ECR/``docs/ENGINEERING_SPEC/``docs/TRACEABILITY.md``docs/CHANGELOG/`
- **正式引擎包**`chanlun/`strategies / web 已用 `from chanlun import ...`
- 根目录 `Chan*.py` / `TF_DF.py` 仍为 **兼容 shim**(旧脚本可用)
- 变更分级:无 ECR 不改 strategies/config;无 ADR 不改缠论算法语义
## Core Architecture
### Chan Theory Engine (`chanlun/`)
```text
chanlun/
core/ # KLU KLC BI SBI SEG ZS BIZS BSP Enum CTime
pipeline/ # orchestrator(ChanLun) + timeframe(TF_DF) + builders/
indicators/ # ChanMACD*
analysis/ # Zone Classifier Pivot Heng PY Find_Trend ...
```
Data processing pipeline (each step feeds the next):
1. **`chanlun.core.ChanKLU`** — Raw K-line unit with TA indicators and pattern recognition
2. **`chanlun.core.ChanKLC`** — Combined K-line: inclusion + fractal; `.next`/`.pre` linked list
3. **`chanlun.core.ChanBI`** — Stroke (笔)
4. **`chanlun.core.ChanSBI`** — Special stroke → SEG
5. **`chanlun.core.ChanSEG`** — Segment (线段)
6. **`chanlun.core.ChanZS`** / **`ChanBIZS`** — Centers (中枢)
7. **`chanlun.core.ChanBSP`** — Buy/Sell points
8. **`chanlun.pipeline.orchestrator.ChanLun`** — Orchestrator
9. **`chanlun.pipeline.timeframe.TF_DF`** — Timeframe facade;实现拆在 `pipeline/builders/`
### Services
- **外部 DATA_SERVICE** — 行情服务(env: `DATA_SERVICE_URL`);本仓库可不含 data_provider 源码
- **`web/`** — Flask UI`create_app()` + `api/` blueprints + `services/`;前端 `static/js/app/`。默认端口见 `web/config.py``FLASK_PORT`,常见 8128
- **`strategies/`** — Freqtrade strategies(本 ECR 不改)
- **`config/`** — Freqtrade configs(本 ECR 不改)
### Data Flow
```
Exchange / DATA_SERVICE → Freqtrade Strategy / web → ChanLun → TF_DF
→ KLU → KLC → BI → SBI → SEG → ZS → BSP
```
## Common Commands
### Freqtrade Trading
```bash
# Live trade
freqtrade trade -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies
# Backtest
freqtrade backtesting -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies --timerange=20251008-
# Download data
freqtrade download-data -c ./user_data/Chan/config/<config>.json -t 1m 1h 1d --pairs BTC/USDT:USDT --timerange=20240101-
# Hyperopt
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/<config>.json -e 200 --timerange=20250201-20250901
# Plot
freqtrade plot-dataframe --strategy <StrategyName> --datadir user_data/data/binance -c ./user_data/Chan/config/<config>.json --timerange=20250721-
```
### Data Provider
```bash
# Docker
cd data_provider && docker compose up -d
# Direct
cd data_provider && python main.py
# With custom config
CONFIG_PATH=./config.json python main.py
```
### Web UI
```bash
cd web && python app.py
# or via gunicorn:
gunicorn -w 4 -b 0.0.0.0:8123 app:app
# Deploy scripts:
cd web && ./deploy.sh # standard
cd web && ./deploy_venv.sh # Ubuntu 22.04+ (venv)
```
### Docker (Freqtrade)
```bash
sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies --timerange=20250721-
```
## Key Conventions
- All Chan theory classes are prefixed with `Chan` (e.g., `ChanBI`, `ChanZS`)
- Strategies import `ChanLun` and add `sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))` to import from parent
- MACD params: `MACD(26, 52, 9)` by default (slow period 52 instead of standard 26)
- Enums in `ChanEnum.py` use `auto()` values
- `ChanKLC` is a linked-list style data structure with `.next`/`.pre` pointers
- The `TF_DF` class is the primary data container per timeframe
- K-line direction uses `Chan_KLINE_DIR` (UP/DOWN/COMBINE/INCLUDED)
- All text comments/commits are in Chinese
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBI import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBIZS import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBSP import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanCTime import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanEnum import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanHeng import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanKLC import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanKLU import * # noqa: F403
-3
View File
@@ -1,3 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.pipeline.orchestrator import ChanLun # noqa: F401
from chanlun.pipeline.timeframe import TF_DF # noqa: F401
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanLun_Classifier import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACD import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDHistSet import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDSeg import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDUnitTF import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPY import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPivotClassifier import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPivotMonitor import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanSBI import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanSEG import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanZS import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanZone import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.Chan_FX_Box import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.Find_Trend import * # noqa: F403
-2
View File
@@ -1,2 +0,0 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.pipeline.timeframe import TF_DF # noqa: F401
View File
-7
View File
@@ -1,7 +0,0 @@
"""威科夫分析(启发式):交易区间 / 阶段 / 事件 / Volume Profile / Live。"""
from __future__ import annotations
from .engine import analyze_wyckoff
from .live import execution_signal_from_wyckoff
__all__ = ["analyze_wyckoff", "execution_signal_from_wyckoff"]
-196
View File
@@ -1,196 +0,0 @@
"""威科夫分析入口:Cycle → Phase → Event → VP + LiveMULTI-CYCLE / LIVE-STRUCTURE)。
range.py 只产 TradingRangeConfirmed 走 events.pyLive 走 live.py。
cycles[0]=ACTIVE;禁止 cycles[-1] 取 active。
Execution 只消费 Confirmed(见 live.execution_signal_from_wyckoff)。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
import pandas as pd
from .events import build_phases, detect_bias_and_events
from .live import analyze_live_structure
from .range import detect_trading_ranges
from .volume_profile import compute_volume_profile
def _fmt_time(v) -> Optional[str]:
if v is None:
return None
if hasattr(v, "isoformat"):
try:
return v.isoformat()
except Exception:
pass
return str(v)
def _empty(vp_bins: int) -> Dict[str, Any]:
return {
"cycles": [],
"trading_range": None,
"bias": "unknown",
"phases": [],
"events": [],
"volume_profile": {"bins": [], "poc": None, "vah": None, "val": None, "bin_count": vp_bins},
"volume_confirm": {"avg_volume": 0.0, "event_checks": {}},
"live": None,
}
def _confidence_for_confirmed(
tr: Dict[str, Any],
phases: List[Dict[str, Any]],
events: List[Dict[str, Any]],
) -> Dict[str, float]:
range_c = float(tr.get("range_confidence") or 0.5)
labels = {p.get("phase") for p in phases}
phase_c = 0.35
if "A" in labels and "B" in labels:
phase_c += 0.15
if "C" in labels:
phase_c += 0.2
if "D" in labels or "E" in labels:
phase_c += 0.15
phase_c = min(0.95, phase_c)
types = {e.get("type") for e in events}
event_c = 0.25
for t in ("Spring", "UTAD", "SOS", "SOW", "LPS", "LPSY"):
if t in types:
event_c += 0.12
event_c = min(0.95, event_c)
overall = 0.4 * range_c + 0.3 * phase_c + 0.3 * event_c
return {
"range": round(range_c, 3),
"phase": round(phase_c, 3),
"event": round(event_c, 3),
"overall": round(overall, 3),
}
def _build_cycle(
work: pd.DataFrame,
tr: Dict[str, Any],
cycle_id: int,
vp_bins: int,
) -> Dict[str, Any]:
bias, events, volume_confirm = detect_bias_and_events(work, tr)
phases = build_phases(work, tr, bias, events)
vp = compute_volume_profile(
work,
int(tr["abs_start_idx"]),
int(tr["abs_end_idx"]),
bin_count=vp_bins,
)
for ev in events:
ev["time"] = _fmt_time(ev.get("time"))
for ph in phases:
ph["start_time"] = _fmt_time(ph.get("start_time"))
ph["end_time"] = _fmt_time(ph.get("end_time"))
is_active = cycle_id == 0
trading_range = {
"start_time": _fmt_time(tr.get("start_time")),
"end_time": _fmt_time(tr.get("end_time")),
"high": float(tr["high"]),
"low": float(tr["low"]),
"mid": float(tr["mid"]),
"active": bool(is_active),
"bars": int(tr.get("bars", 0)),
}
conf = _confidence_for_confirmed(tr, phases, events)
# Live 层:仅 ACTIVE 周期做推演;历史周期归档为 COMPLETED
if is_active:
live = analyze_live_structure(
work, tr, confirmed_events=events, confirmed_phases=phases, bias=bias,
)
lifecycle = live.get("lifecycle") or "FORMING"
else:
live = None
lifecycle = "COMPLETED"
return {
"id": int(cycle_id),
"role": "latest" if is_active else "historical",
# MULTI-CYCLE:时间线角色
"status": "ACTIVE" if is_active else "HISTORICAL",
# LIVE-STRUCTURE:生命周期
"lifecycle": lifecycle,
"direction": "latest" if is_active else "historical",
"period": {
"start_time": _fmt_time(tr.get("start_time")),
"end_time": _fmt_time(tr.get("end_time")),
"bars": int(tr.get("bars", 0)),
},
"confidence": conf,
"trading_range": trading_range,
"bias": bias,
# 兼容旧读法:顶层 phases/events = confirmed
"phases": phases,
"events": events,
"confirmed": {
"phases": phases,
"events": events,
"volume_confirm": volume_confirm,
},
"live": live,
"volume_profile": vp,
"volume_confirm": volume_confirm,
}
def analyze_wyckoff(
df: pd.DataFrame,
lookback: int = 120,
vp_bins: int = 50,
min_bars: int = 24,
atr_mult: float = 1.2,
range_start_time=None,
prefer_start_time=None,
max_cycles: int = 8,
) -> Dict[str, Any]:
"""
多周期威科夫分析。
cycles[0] = ACTIVE;顶层 phases/events 只镜像 Confirmed。
顶层 live 镜像 cycles[0].live。
"""
empty = _empty(vp_bins)
if df is None or len(df) < 30:
return empty
if not all(c in df.columns for c in ("open", "high", "low", "close")):
return empty
work = df.copy()
if "volume" not in work.columns:
work["volume"] = 1.0
trs = detect_trading_ranges(
work,
lookback=lookback,
min_bars=max(8, int(min_bars)),
atr_mult=atr_mult,
max_cycles=max(1, min(8, int(max_cycles))),
prefer_start_time=prefer_start_time,
range_start_time=range_start_time,
)
if not trs:
return empty
cycles: List[Dict[str, Any]] = []
for i, tr in enumerate(trs):
cycles.append(_build_cycle(work, tr, cycle_id=i, vp_bins=vp_bins))
active = cycles[0]
return {
"cycles": cycles,
"trading_range": active["trading_range"],
"bias": active["bias"],
"phases": active["confirmed"]["phases"],
"events": active["confirmed"]["events"],
"volume_profile": active["volume_profile"],
"volume_confirm": active["volume_confirm"],
"live": active.get("live"),
"lifecycle": active.get("lifecycle"),
}
-369
View File
@@ -1,369 +0,0 @@
"""威科夫阶段与事件(启发式)。"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
def _bar_time(df: pd.DataFrame, i: int):
row = df.iloc[i]
if "date" in df.columns and pd.notna(row["date"]):
return row["date"]
if "timestamp" in df.columns:
return row["timestamp"]
return i
def _avg_vol(df: pd.DataFrame, i: int, win: int = 20) -> float:
a = max(0, i - win + 1)
v = df["volume"].astype(float).iloc[a : i + 1]
m = float(v.mean()) if len(v) else 0.0
return m if m > 0 else 1.0
def detect_bias_and_events(
df: pd.DataFrame,
tr: Dict[str, Any],
) -> Tuple[str, List[Dict[str, Any]], Dict[str, Any]]:
"""
返回 bias、events、volume_confirm。
Spring/UTAD 相对「结构高低」判定:取区间内次低/次高(剔除单根极值),
避免箱体把假破低点吃进 lo 后永远刺不破、从而无 C 阶段。
"""
hi = float(tr["high"])
lo = float(tr["low"])
mid = float(tr["mid"])
tol = float(tr.get("tol") or (hi - lo) * 0.05)
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
events: List[Dict[str, Any]] = []
# 结构边界:用次低/次高作假破参照(至少 8 根才启用)
seg = df.iloc[s : e + 1]
event_lo, event_hi = lo, hi
if len(seg) >= 8:
lows = seg["low"].astype(float)
highs = seg["high"].astype(float)
# nsmallest(2) 的较大者 = 次低;nlargest(2) 的较小者 = 次高
event_lo = float(lows.nsmallest(min(2, len(lows))).iloc[-1])
event_hi = float(highs.nlargest(min(2, len(highs))).iloc[-1])
# 勿比公布箱沿更「松」:结构带应在箱内
event_lo = max(event_lo, lo)
event_hi = min(event_hi, hi)
# 若次低仍等于极值(多根同价),略抬参照便于识别收回
if abs(event_lo - lo) < 1e-12:
event_lo = lo + max(tol * 0.35, (hi - lo) * 0.02)
if abs(event_hi - hi) < 1e-12:
event_hi = hi - max(tol * 0.35, (hi - lo) * 0.02)
# 扫描区间内及之后(含 tail_reserve
scan_end = int(tr.get("abs_scan_end_idx", min(len(df) - 1, e + 15)))
scan_end = min(len(df) - 1, max(scan_end, e))
spring = None
utad = None
sos = None
sod = None # sign of weakness / distribution breakdown
lps = None
lpsy = None
for i in range(s + 2, scan_end + 1):
row = df.iloc[i]
low = float(row["low"])
high = float(row["high"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
# Spring: pierce below structural support then close back
if spring is None and low < event_lo - tol * 0.35 and close >= event_lo - tol * 0.35:
vol_ok = ratio <= 1.35 or (i + 1 <= scan_end and float(df.iloc[min(i + 1, scan_end)]["volume"]) / avg_v < 1.2)
spring = {
"type": "Spring",
"time": _bar_time(df, i),
"price": low,
"note": "假破下沿后收回",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# UTAD: pierce above structural resistance then close back
if utad is None and high > event_hi + tol * 0.35 and close <= event_hi + tol * 0.35:
vol_ok = ratio >= 0.8
utad = {
"type": "UTAD",
"time": _bar_time(df, i),
"price": high,
"note": "假破上沿后跌回",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# SOS: close above high with volume
if sos is None and close > hi + tol * 0.15:
vol_ok = ratio >= 1.15
sos = {
"type": "SOS",
"time": _bar_time(df, i),
"price": close,
"note": "放量上破交易区间",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# SOW / breakdown
if sod is None and close < lo - tol * 0.15:
vol_ok = ratio >= 1.15
sod = {
"type": "SOW",
"time": _bar_time(df, i),
"price": close,
"note": "放量下破交易区间",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# LPS after SOS: pullback that holds above mid/high-band with lighter volume
if sos is not None:
si = int(sos["idx"])
for i in range(si + 1, min(len(df), si + 25)):
row = df.iloc[i]
low = float(row["low"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
if low >= mid - tol and close >= hi - tol * 2:
vol_ok = ratio <= 1.05
lps = {
"type": "LPS",
"time": _bar_time(df, i),
"price": low,
"note": "突破后缩量回踩不破",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
break
if sod is not None:
si = int(sod["idx"])
for i in range(si + 1, min(len(df), si + 25)):
row = df.iloc[i]
high = float(row["high"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
if high <= mid + tol and close <= lo + tol * 2:
vol_ok = ratio <= 1.05
lpsy = {
"type": "LPSY",
"time": _bar_time(df, i),
"price": high,
"note": "下跌突破后缩量反抽不过",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
break
# 冲突清理:已判定吸筹且有 SOS 时,丢弃更早的 UTAD(避免阶段/图面误导)
# 派发且有 SOW 时,丢弃更晚才合理的 Spring 假信号同理在偏置后再滤
keep = []
for ev in (spring, sos, lps, utad, sod, lpsy):
if not ev:
continue
keep.append(ev)
# bias(先算)
last_c = float(df["close"].iloc[-1])
bias = "unknown"
if sos and (not sod or int(sos.get("idx", 0)) >= int(sod.get("idx", 0))):
bias = "accumulation"
elif sod and (not sos or int(sod.get("idx", 0)) > int(sos.get("idx", 0))):
bias = "distribution"
elif spring and not utad:
bias = "accumulation"
elif utad and not spring:
bias = "distribution"
elif last_c >= mid:
bias = "accumulation"
else:
bias = "distribution"
filtered = []
for ev in keep:
if bias == "accumulation" and ev["type"] == "UTAD" and sos and int(ev["idx"]) <= int(sos["idx"]):
continue
if bias == "distribution" and ev["type"] == "Spring" and sod and int(ev["idx"]) <= int(sod["idx"]):
continue
filtered.append(ev)
events = [{k: v for k, v in ev.items() if k != "idx"} for ev in filtered]
avg_volume = float(df["volume"].astype(float).iloc[max(0, e - 20) : e + 1].mean()) if "volume" in df.columns else 0.0
volume_confirm = {
"avg_volume": avg_volume,
"event_checks": {ev["type"]: {"volume_ok": ev.get("volume_ok"), "volume_ratio": ev.get("volume_ratio")} for ev in events},
}
return bias, events, volume_confirm
def build_phases(
df: pd.DataFrame,
tr: Dict[str, Any],
bias: str,
events: List[Dict[str, Any]],
min_bars: int = 3,
) -> List[Dict[str, Any]]:
"""
按威科夫事件锚点切分 A–E(启发式)。
吸筹:A停止 → B筑底 → C测试(Spring) → D拉升(SOS…LPS) → E离开
派发:A停止 → B筑顶 → C测试(UTAD) → D派发(SOW…LPSY) → E离开
无 Spring/UTAD 时:若已有 SOS/SOW,用突破前末次沿带测试补 C;仍无则省略 C。
"""
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
hi = float(tr["high"])
lo = float(tr["low"])
n_last = len(df) - 1
min_span = max(2, min_bars - 1)
range_len = max(1, e - s)
def _match_idx(t) -> Optional[int]:
if t is None:
return None
lo = max(0, s - 2)
hi = min(len(df), e + 40)
for i in range(lo, hi):
if _bar_time(df, i) == t:
return i
try:
tt = pd.Timestamp(t)
sample = None
if "date" in df.columns and len(df):
sample = df["date"].iloc[min(s, n_last)]
if sample is not None and getattr(sample, "tzinfo", None) is not None and tt.tzinfo is None:
tt = tt.tz_localize(sample.tzinfo)
for i in range(lo, hi):
bt = _bar_time(df, i)
try:
if abs((pd.Timestamp(bt) - tt).total_seconds()) <= 1:
return i
except Exception:
continue
except Exception:
pass
return None
event_idx: Dict[str, int] = {}
for ev in events:
idx = _match_idx(ev.get("time"))
if idx is not None:
event_idx[str(ev.get("type"))] = idx
accum = bias != "distribution"
if accum:
c_ev = event_idx.get("Spring")
d_ev = event_idx.get("SOS")
d_tail = event_idx.get("LPS") or d_ev
else:
c_ev = event_idx.get("UTAD")
d_ev = event_idx.get("SOW")
d_tail = event_idx.get("LPSY") or d_ev
# 有 D 无明确测试事件时:用突破前最后一次触及下/上沿作为 C(次级测试)
if c_ev is None and d_ev is not None:
band = lo + (hi - lo) * 0.28 if accum else hi - (hi - lo) * 0.28
for i in range(int(d_ev) - 1, s + 1, -1):
row = df.iloc[i]
if accum and float(row["low"]) <= band:
c_ev = i
break
if not accum and float(row["high"]) >= band:
c_ev = i
break
def _lab(phase: str) -> str:
if accum:
m = {"A": "A停止下跌", "B": "B筑底", "C": "C测试", "D": "D拉升", "E": "E离开"}
else:
m = {"A": "A停止上涨", "B": "B筑顶", "C": "C测试", "D": "D派发", "E": "E离开"}
return m.get(phase, phase)
a_end = s + max(min_bars, range_len // 5)
c_start = c_end = None
if c_ev is not None:
c_start = max(s, int(c_ev) - 1)
c_end = min(n_last, int(c_ev) + 1)
if d_ev is not None:
d_start = int(d_ev)
d_end = min(n_last, max(int(d_tail or d_ev), d_start) + max(min_bars, range_len // 8))
if d_tail is not None:
d_end = max(d_end, min(n_last, int(d_tail) + 1))
else:
d_start = d_end = None
if c_start is not None:
b_end = max(a_end + 1, c_start)
elif d_start is not None:
b_end = max(a_end + 1, d_start)
else:
b_end = max(a_end + 1, e)
if d_end is not None:
e_start = min(n_last, d_end)
e_end = n_last
else:
e_start = e_end = None
raw = [("A", s, a_end), ("B", a_end, b_end)]
if c_start is not None and c_end is not None:
raw.append(("C", c_start, c_end))
if d_start is not None and d_end is not None:
raw.append(("D", d_start, d_end))
if e_start is not None and e_end is not None and e_end > e_start:
raw.append(("E", e_start, e_end))
phases: List[Dict[str, Any]] = []
cursor = s
for phase, _a, _b in raw:
if cursor >= n_last:
break
a = max(int(_a), cursor)
b = int(max(int(_b), a))
need = 1 if phase == "C" else min_span
if b < a + need:
b = min(n_last, a + need)
b = int(np.clip(b, a, n_last))
if b < a:
continue
if phases and phases[-1].get("_a") == a and phases[-1].get("_b") == b:
continue
phases.append(
{
"phase": phase,
"label": _lab(phase),
"start_time": _bar_time(df, a),
"end_time": _bar_time(df, b),
"_a": a,
"_b": b,
}
)
cursor = b
for p in phases:
p.pop("_a", None)
p.pop("_b", None)
return phases
-258
View File
@@ -1,258 +0,0 @@
"""威科夫 Live / Developing 层(WYCKOFF-LIVE-STRUCTURE-001)。
独立于 Confirmed Engine:不修改 events 确认条件,不写入 confirmed.events。
Execution 不得消费本模块输出。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Set
import numpy as np
import pandas as pd
def _avg_vol(df: pd.DataFrame, i: int, win: int = 20) -> float:
a = max(0, i - win + 1)
v = df["volume"].astype(float).iloc[a : i + 1]
m = float(v.mean()) if len(v) else 0.0
return m if m > 0 else 1.0
def _empty_live() -> Dict[str, Any]:
return {
"lifecycle": "UNKNOWN",
"range_formation": None,
"phase_candidate": None,
"event_candidates": [],
"next_expected": None,
"confidence": {
"cycle": 0.0,
"phase": 0.0,
"event": 0.0,
"structure": 0.0,
"volume": 0.0,
"overall": 0.0,
},
"note": "",
}
def analyze_live_structure(
df: pd.DataFrame,
tr: Optional[Dict[str, Any]],
confirmed_events: Optional[List[Dict[str, Any]]] = None,
confirmed_phases: Optional[List[Dict[str, Any]]] = None,
bias: str = "unknown",
) -> Dict[str, Any]:
"""
基于当前 TradingRange 与已确认事件,推演 Live candidates。
confirmed_* 只读,用于避免重复提示已确认事件,不修改之。
"""
out = _empty_live()
if df is None or len(df) < 20 or tr is None:
out["note"] = "insufficient structure"
return out
confirmed_events = confirmed_events or []
confirmed_phases = confirmed_phases or []
confirmed_types: Set[str] = {str(e.get("type")) for e in confirmed_events if e.get("type")}
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
scan_end = int(tr.get("abs_scan_end_idx", len(df) - 1))
scan_end = min(len(df) - 1, max(scan_end, e))
hi = float(tr["high"])
lo = float(tr["low"])
mid = float(tr["mid"])
tol = float(tr.get("tol") or (hi - lo) * 0.05)
atr = float(tr.get("atr") or max((hi - lo) * 0.2, 1e-9))
seg = df.iloc[s : e + 1]
if len(seg) < 8:
out["note"] = "range too short"
return out
# —— Range Formation(横盘 / 波动收敛)——
closes = seg["close"].astype(float)
highs = seg["high"].astype(float)
lows = seg["low"].astype(float)
vols = seg["volume"].astype(float) if "volume" in seg.columns else pd.Series([1.0] * len(seg))
half = max(4, len(seg) // 2)
vol_early = float(np.std(closes.iloc[:half])) if half > 1 else 0.0
vol_late = float(np.std(closes.iloc[-half:])) if half > 1 else 0.0
width = hi - lo
width_atr = width / atr if atr > 0 else 99.0
converging = vol_early > 1e-12 and vol_late < vol_early * 0.85
range_ok = 1.2 <= width_atr <= 10.0 and len(seg) >= 16
structure_score = 0.35
if range_ok:
structure_score += 0.25
if converging:
structure_score += 0.2
if width_atr <= 6.0:
structure_score += 0.1
structure_score = float(min(0.95, structure_score))
out["range_formation"] = {
"potential_trading_range": bool(range_ok),
"converging": bool(converging),
"width_atr": round(width_atr, 3),
"bars": int(len(seg)),
}
# —— 最近 K 形态(Phase C / Event candidates)——
i = scan_end
row = df.iloc[i]
o = float(row["open"])
h = float(row["high"])
l = float(row["low"])
c = float(row["close"])
rng = max(h - l, 1e-9)
lower_wick = min(o, c) - l
upper_wick = h - max(o, c)
avg_v = _avg_vol(df, i)
vol = float(row["volume"]) if "volume" in df.columns else avg_v
vol_ratio = vol / avg_v if avg_v else 1.0
volume_score = float(np.clip(1.1 - abs(vol_ratio - 1.0) * 0.35, 0.2, 0.95))
phase_candidate = None
phase_conf = 0.0
# Phase C:测低 + 下影 + 缩量(吸筹语境)
near_lo = l <= lo + tol * 1.2
test_low = l < mid and lower_wick >= rng * 0.35
vol_contract = vol_ratio <= 1.05
if bias != "distribution" and near_lo and test_low and vol_contract:
phase_candidate = "C"
phase_conf = 0.55 + (0.1 if lower_wick >= rng * 0.5 else 0) + (0.08 if vol_ratio < 0.9 else 0)
# Phase D 候选:价格在箱上半、有上破意图但未确认 SOS
elif c >= mid and (h >= hi - tol or c > hi - tol * 0.5):
phase_candidate = "D"
phase_conf = 0.5 + (0.1 if c > mid else 0)
elif c < mid and (l <= lo + tol):
phase_candidate = "B"
phase_conf = 0.45
# 已有 confirmed phase 时,candidate 取「下一阶段」提示,不覆盖事实
confirmed_phase_set = {str(p.get("phase")) for p in confirmed_phases}
if "E" in confirmed_phase_set:
phase_candidate = phase_candidate or "E"
phase_conf = max(phase_conf, 0.7)
elif "D" in confirmed_phase_set and phase_candidate is None:
phase_candidate = "D"
phase_conf = max(phase_conf, 0.65)
out["phase_candidate"] = phase_candidate
phase_conf = float(min(0.92, phase_conf))
# —— Event candidates(仅 Spring / SOS / LPS / UTAD)——
candidates: List[Dict[str, Any]] = []
def _add(typ: str, conf: float, note: str) -> None:
if typ in confirmed_types:
return # 已确认则不再作为 candidate
candidates.append(
{
"type": typ,
"confidence": round(float(min(0.9, conf)), 3),
"confirmed": False,
"note": note,
}
)
# Spring candidate:刺破或贴近下沿,收盘收回,但未达 Confirmed 规则(或不在 confirmed
pierce_lo = l < lo - tol * 0.15
close_back = c >= lo - tol * 0.5
if pierce_lo and close_back:
_add("Spring", 0.5 + (0.12 if vol_ratio <= 1.2 else 0) + (0.08 if close_back else 0), "假破下沿收回(未确认)")
elif l <= lo + tol * 0.35 and close_back and lower_wick >= rng * 0.4:
_add("Spring", 0.45 + (0.1 if vol_contract else 0), "测下沿长下影(未确认)")
# UTAD candidate
pierce_hi = h > hi + tol * 0.15
close_back_dn = c <= hi + tol * 0.5
if pierce_hi and close_back_dn:
_add("UTAD", 0.5 + (0.1 if vol_ratio >= 0.9 else 0), "假破上沿跌回(未确认)")
# SOS candidate:接近/轻破上沿,量能一般,未确认
if c > hi - tol * 0.4 or h >= hi:
sos_conf = 0.48 + (0.12 if c > hi else 0) + (0.1 if vol_ratio >= 1.05 else 0)
_add("SOS", sos_conf, "上破/逼近箱顶(未确认)")
# LPS candidate:站上 mid/上沿带后回踩
if c >= mid and l >= mid - tol * 1.5 and l > lo + (hi - lo) * 0.25:
_add("LPS", 0.46 + (0.1 if vol_ratio <= 1.0 else 0), "箱内上沿带回踩(未确认)")
candidates.sort(key=lambda x: x["confidence"], reverse=True)
out["event_candidates"] = candidates[:4]
event_score = float(candidates[0]["confidence"]) if candidates else 0.25
# next_expected(简规则)
next_exp = None
if "Spring" in confirmed_types and "SOS" not in confirmed_types:
next_exp = "SOS"
elif "SOS" in confirmed_types and "LPS" not in confirmed_types:
next_exp = "LPS"
elif "UTAD" in confirmed_types and "SOW" not in confirmed_types:
next_exp = "SOW"
elif any(c["type"] == "Spring" for c in candidates):
next_exp = "Test"
elif any(c["type"] == "SOS" for c in candidates):
next_exp = "LPS"
out["next_expected"] = next_exp
# —— lifecycle ——
key_confirmed = confirmed_types & {"Spring", "SOS", "UTAD", "SOW", "LPS", "LPSY"}
if key_confirmed:
lifecycle = "CONFIRMED"
elif range_ok or phase_candidate or candidates:
lifecycle = "FORMING"
else:
lifecycle = "UNKNOWN"
out["lifecycle"] = lifecycle
cycle_c = structure_score
overall = 0.35 * cycle_c + 0.25 * phase_conf + 0.25 * event_score + 0.15 * volume_score
out["confidence"] = {
"cycle": round(cycle_c, 3),
"phase": round(phase_conf, 3),
"event": round(event_score, 3),
"structure": round(structure_score, 3),
"volume": round(volume_score, 3),
"overall": round(float(overall), 3),
}
parts = []
if out["range_formation"]["potential_trading_range"]:
parts.append("Potential TR")
if phase_candidate:
parts.append(f"Phase {phase_candidate} candidate")
if candidates:
parts.append(f"{candidates[0]['type']} candidate")
out["note"] = "; ".join(parts) if parts else "observing"
return out
def execution_signal_from_wyckoff(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
Execution 边界:只允许 Confirmed。
返回 source='confirmed' 的信号描述;Live-only 时返回 None。
"""
if not payload:
return None
cycles = payload.get("cycles") or []
active = cycles[0] if cycles else None
events = []
if active and isinstance(active.get("confirmed"), dict):
events = list(active["confirmed"].get("events") or [])
if not events:
# 兼容旧顶层 events(均为 confirmed 镜像)
events = list(payload.get("events") or [])
if not events:
return None
last = events[-1]
return {
"source": "confirmed",
"type": last.get("type"),
"time": last.get("time"),
"lifecycle": (active or {}).get("lifecycle") or "CONFIRMED",
}
-442
View File
@@ -1,442 +0,0 @@
"""交易区间检测:仅负责 TradingRange(起止/高低/结构分)。
WYCKOFF-MULTI-CYCLE-001Phase/Event/VP 不得进入本模块。
过滤顺序固定:detect → quality → trend → overlap(<0.2) → accept → mask。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
MAX_CYCLES = 8
OVERLAP_RATIO_MAX = 0.2
def _atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
high = df["high"].astype(float)
low = df["low"].astype(float)
close = df["close"].astype(float)
prev_close = close.shift(1)
tr = pd.concat(
[
(high - low).abs(),
(high - prev_close).abs(),
(low - prev_close).abs(),
],
axis=1,
).max(axis=1)
return tr.rolling(period, min_periods=max(3, period // 2)).mean()
def _robust_width(seg: pd.DataFrame) -> float:
"""用 90/10 分位估宽,避免单根影线把长窗卡死。"""
h = seg["high"].astype(float)
l = seg["low"].astype(float)
if len(seg) < 6:
return float(h.max() - l.min())
return float(np.nanpercentile(h, 90) - np.nanpercentile(l, 10))
def _score_segment(
length: int,
near_hi: int,
near_lo: int,
inside: float,
width: float,
atr: float,
) -> float:
"""结构质量分(非 Phase/Event)。"""
touch = min(near_hi, 6) + min(near_lo, 6)
width_pen = (width / atr) if atr > 0 else width
return float(touch) * 4.0 + float(inside) * 25.0 - width_pen * 3.0 + min(length / 40.0, 2.0)
def _time_col(df: pd.DataFrame) -> Optional[str]:
if "date" in df.columns:
return "date"
if "timestamp" in df.columns:
return "timestamp"
return None
def _bar_index_at_or_after(work: pd.DataFrame, ts: Any) -> Optional[int]:
col = _time_col(work)
if col is None or ts is None:
return None
try:
target = pd.Timestamp(ts)
except Exception:
return None
series = pd.to_datetime(work[col], utc=True, errors="coerce")
if target.tzinfo is None:
target = target.tz_localize("UTC")
else:
target = target.tz_convert("UTC")
if series.isna().all():
return None
ge = series >= target
if ge.any():
return int(np.flatnonzero(ge.to_numpy())[0])
return 0
def _pack_range(
work: pd.DataFrame,
df: pd.DataFrame,
start_i: int,
end_i: int,
hi: float,
lo: float,
tol: float,
last_atr: float,
score: float,
n: int,
window_offset: int = 0,
) -> Dict[str, Any]:
"""组装 TradingRange(仅结构字段)。"""
mid = (hi + lo) / 2.0
last_c = float(work["close"].iloc[min(end_i, len(work) - 1)])
price_in_box = (lo - tol * 1.5) <= last_c <= (hi + tol * 1.5)
bars = int(end_i - start_i + 1)
# 结构置信:归一化 score(启发式)
range_conf = float(np.clip(score / 55.0, 0.05, 0.99))
best = {
"start_idx": int(start_i),
"end_idx": int(end_i),
"high": float(hi),
"low": float(lo),
"mid": float(mid),
"active": bool(price_in_box),
"atr": float(last_atr),
"tol": float(tol),
"bars": bars,
"score": float(score),
"quality": float(score),
"range_confidence": range_conf,
}
def _ts(row) -> Any:
col = _time_col(work)
if col and pd.notna(row[col]):
return row[col]
return None
best["start_time"] = _ts(work.iloc[best["start_idx"]])
best["end_time"] = _ts(work.iloc[best["end_idx"]])
# window_offsetslice 相对父 DataFrame 的起点;勿用 len(df)-len(work)
offset = int(window_offset)
best["abs_start_idx"] = offset + best["start_idx"]
best["abs_end_idx"] = offset + best["end_idx"]
best["abs_scan_end_idx"] = offset + n - 1
return best
def _overlap_ratio(a0: int, a1: int, b0: int, b1: int) -> float:
"""两闭区间重叠长度 / 较短区间长度。"""
lo = max(a0, b0)
hi = min(a1, b1)
if hi < lo:
return 0.0
overlap = hi - lo + 1
shorter = min(a1 - a0 + 1, b1 - b0 + 1)
if shorter <= 0:
return 0.0
return float(overlap) / float(shorter)
def _passes_quality(tr: Dict[str, Any], min_bars: int) -> bool:
if tr is None:
return False
if int(tr.get("bars") or 0) < max(8, min_bars // 2):
return False
if float(tr.get("score") or 0) < 12.0:
return False
hi = float(tr["high"])
lo = float(tr["low"])
atr = float(tr.get("atr") or 0) or 1.0
if (hi - lo) / atr > 12.0:
return False
return True
def _passes_trend_filter(work: pd.DataFrame, tr: Dict[str, Any]) -> bool:
"""趋势污染:定向位移过大则非震荡箱。"""
s = int(tr["start_idx"])
e = int(tr["end_idx"])
seg = work.iloc[s : e + 1]
if len(seg) < 8:
return False
c0 = float(seg["close"].iloc[0])
c1 = float(seg["close"].iloc[-1])
atr = float(tr.get("atr") or 0) or 1.0
drift = abs(c1 - c0) / atr
# 相对箱宽:漂移占箱宽过大 → 趋势
width = max(float(tr["high"]) - float(tr["low"]), atr)
drift_frac = abs(c1 - c0) / width
if drift > 6.0 and drift_frac > 0.55:
return False
return True
def _detect_in_window(
df: pd.DataFrame,
win_start: int,
win_end: int,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
prefer_start_time: Any = None,
range_start_time: Any = None,
) -> Optional[Dict[str, Any]]:
"""
在 df[win_start:win_end+1] 内检测单个 TradingRange。
只返回箱体结构,不含 Phase/Event/VP。
"""
if df is None or win_end < win_start:
return None
slice_df = df.iloc[win_start : win_end + 1].reset_index(drop=True)
lookback = len(slice_df)
if lookback < min_bars + 5:
return None
work = slice_df
n = len(work)
reserve = min(tail_reserve, max(0, n - min_bars - 2))
core_end = n - reserve if reserve > 0 else n
core = work.iloc[:core_end]
if len(core) < min_bars:
core = work
core_end = n
reserve = 0
atr = _atr(work)
last_atr = float(atr.iloc[core_end - 1]) if atr.notna().iloc[:core_end].any() else float(
(core["high"] - core["low"]).mean()
)
if not np.isfinite(last_atr) or last_atr <= 0:
last_atr = float(core["close"].iloc[-1]) * 0.01
eff_atr_mult = float(atr_mult)
if lookback >= 280:
eff_atr_mult = atr_mult * 1.7
elif lookback >= 160:
eff_atr_mult = atr_mult * 1.3
width_factor = 3.8 + min(2.2, max(0.0, (lookback - 80) / 100.0))
max_width = last_atr * eff_atr_mult * width_factor
tol = last_atr * eff_atr_mult * 0.35
prefer_i = None
if prefer_start_time is not None:
prefer_i = _bar_index_at_or_after(work, prefer_start_time)
if range_start_time is not None:
start_i = _bar_index_at_or_after(work, range_start_time)
if start_i is not None and start_i <= core_end - 8:
seg = work.iloc[start_i:core_end]
hi = float(seg["high"].max())
lo = float(seg["low"].min())
rw = _robust_width(seg)
if 0 < rw <= max_width * 1.15:
near_hi = int((seg["high"] >= hi - tol).sum())
near_lo = int((seg["low"] <= lo + tol).sum())
inside = float(((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean())
if near_hi >= 2 and near_lo >= 2 and inside >= 0.70:
score = _score_segment(len(seg), near_hi, near_lo, inside, rw, last_atr)
return _pack_range(
work, df, start_i, core_end - 1, hi, lo, tol, last_atr, score, n,
window_offset=win_start,
)
eff_min_bars = max(8, int(min_bars))
cn = len(core)
max_bars = min(cn, max(eff_min_bars * 2, min(96, max(eff_min_bars + 8, int(cn * 0.5)))))
cands: List[Tuple[float, int, int, int, float, float, float]] = []
def _try_seg(start_i: int, end_i: int, prefer_boost: float = 0.0) -> None:
if end_i - start_i + 1 < eff_min_bars:
return
if start_i < 0 or end_i >= cn or start_i > end_i:
return
seg = work.iloc[start_i : end_i + 1]
hi = float(seg["high"].max())
lo = float(seg["low"].min())
rw = _robust_width(seg)
if rw <= 0 or rw > max_width:
return
raw_w = hi - lo
if raw_w > max_width * 1.35:
return
near_hi = int((seg["high"] >= hi - tol).sum())
near_lo = int((seg["low"] <= lo + tol).sum())
if near_hi < 2 or near_lo < 2:
return
inside = float(((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean())
if inside < 0.72:
return
length = end_i - start_i + 1
score = _score_segment(length, near_hi, near_lo, inside, rw, last_atr) + prefer_boost
cands.append((score, length, start_i, end_i, hi, lo, rw))
for length in range(min(cn, max_bars), eff_min_bars - 1, -4):
start_i = cn - length
boost = 0.0
if prefer_i is not None:
dist = abs(start_i - int(prefer_i))
if dist <= 6:
boost = 10.0
elif dist <= 14:
boost = 4.0
elif start_i > int(prefer_i) + 16:
boost = -10.0
_try_seg(start_i, cn - 1, boost)
if prefer_i is not None:
pi = int(prefer_i)
if 0 <= pi < cn:
align_max = min(cn, max(max_bars, int(cn * 0.65)))
alen = cn - pi
if eff_min_bars <= alen <= align_max:
_try_seg(pi, cn - 1, prefer_boost=18.0)
elif alen > align_max:
start_i = max(0, cn - align_max)
if start_i > pi:
start_i = pi
end_i = min(cn - 1, pi + align_max - 1)
else:
end_i = cn - 1
_try_seg(start_i, end_i, prefer_boost=12.0)
if not cands:
return None
cands.sort(key=lambda x: x[0], reverse=True)
best_score = cands[0][0]
band = max(4.0, abs(best_score) * 0.10)
near = [c for c in cands if c[0] >= best_score - band]
chosen = max(near, key=lambda x: (x[1], x[0]))
score, _length, start_i, end_i, hi, lo, _rw = chosen
return _pack_range(work, df, start_i, end_i, hi, lo, tol, last_atr, score, n, window_offset=win_start)
def detect_trading_ranges(
df: pd.DataFrame,
lookback: Optional[int] = None,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
max_cycles: int = MAX_CYCLES,
prefer_start_time: Any = None,
range_start_time: Any = None,
) -> List[Dict[str, Any]]:
"""
倒序切多段 TradingRange(近→远)。
过滤顺序:detect → quality → trend → overlap → accept → mask。
返回列表已按时间倒序,调用方将 [0] 标为 ACTIVE。
"""
if df is None or len(df) < min_bars + 5:
return []
lb = int(lookback) if lookback is not None else len(df)
work = df.tail(lb).reset_index(drop=True)
n = len(work)
occupied: List[Dict[str, Any]] = []
accepted: List[Dict[str, Any]] = []
# 搜索右端从 n-1 往左收缩;每接受一段后右端移到该段 start 之前
search_end = n - 1
prefer = prefer_start_time
hard_start = range_start_time
while len(accepted) < max(1, int(max_cycles)) and search_end >= min_bars + 4:
# 在剩余历史内从右往左试多个右边界,避免历史箱必须贴住 search_end
# (否则中间趋势会挡住更早的真实箱)
cand = None
step = max(4, min(12, (search_end - min_bars) // 10 or 4))
for end_try in range(search_end, min_bars + 4, -step):
trial = _detect_in_window(
work,
0,
end_try,
min_bars=min_bars,
atr_mult=atr_mult,
tail_reserve=tail_reserve,
prefer_start_time=prefer if len(accepted) == 0 and end_try == search_end else None,
range_start_time=hard_start if len(accepted) == 0 and end_try == search_end else None,
)
# 1) detect
if trial is None:
continue
# 2) quality
if not _passes_quality(trial, min_bars):
continue
# 3) trend contamination
if not _passes_trend_filter(work, trial):
continue
# 4) overlap with accepted
a0, a1 = int(trial["abs_start_idx"]), int(trial["abs_end_idx"])
overlap_bad = False
for occ in occupied:
ratio = _overlap_ratio(a0, a1, int(occ["start"]), int(occ["end"]))
if ratio >= OVERLAP_RATIO_MAX:
overlap_bad = True
break
if overlap_bad:
continue
# 取最靠右的合格箱(倒序第一段)
cand = trial
break
if cand is None:
break
# 5) accept
accepted.append(cand)
a0, a1 = int(cand["abs_start_idx"]), int(cand["abs_end_idx"])
# 6) mask
occupied.append(
{
"start": a0,
"end": max(a1, int(cand.get("abs_scan_end_idx", a1))),
"quality": float(cand.get("quality") or 0),
"high": float(cand["high"]),
"low": float(cand["low"]),
}
)
# 下一轮只在更早窗口搜
search_end = int(cand["abs_start_idx"]) - 1
hard_start = None
prefer = None
# abs_* 目前相对 work;若 df 比 work 长需加 offset
offset = len(df) - len(work)
if offset:
for tr in accepted:
tr["abs_start_idx"] = int(tr["abs_start_idx"]) + offset
tr["abs_end_idx"] = int(tr["abs_end_idx"]) + offset
tr["abs_scan_end_idx"] = int(tr["abs_scan_end_idx"]) + offset
return accepted
def detect_trading_range(
df: pd.DataFrame,
lookback: int = 120,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
range_start_time: Any = None,
prefer_start_time: Any = None,
) -> Optional[Dict[str, Any]]:
"""兼容旧接口:返回倒序列表中的第一段(ACTIVE 候选)。"""
ranges = detect_trading_ranges(
df,
lookback=lookback,
min_bars=min_bars,
atr_mult=atr_mult,
tail_reserve=tail_reserve,
max_cycles=1,
prefer_start_time=prefer_start_time,
range_start_time=range_start_time,
)
return ranges[0] if ranges else None
@@ -1,72 +0,0 @@
"""区间内 Volume Profile。"""
from __future__ import annotations
from typing import Any, Dict, List
import numpy as np
import pandas as pd
def compute_volume_profile(
df: pd.DataFrame,
start_idx: int,
end_idx: int,
bin_count: int = 50,
value_area_pct: float = 0.70,
) -> Dict[str, Any]:
seg = df.iloc[start_idx : end_idx + 1]
if seg.empty:
return {"bins": [], "poc": None, "vah": None, "val": None, "bin_count": bin_count}
typical = (seg["high"].astype(float) + seg["low"].astype(float) + seg["close"].astype(float)) / 3.0
vol = seg["volume"].astype(float).fillna(0.0)
lo = float(seg["low"].min())
hi = float(seg["high"].max())
if not np.isfinite(lo) or not np.isfinite(hi) or hi <= lo:
mid = float(seg["close"].iloc[-1])
return {
"bins": [{"price": mid, "volume": float(vol.sum())}],
"poc": mid,
"vah": mid,
"val": mid,
"bin_count": 1,
}
edges = np.linspace(lo, hi, bin_count + 1)
# 右开最后一桶闭合
idx = np.clip(np.digitize(typical.values, edges) - 1, 0, bin_count - 1)
vols = np.zeros(bin_count, dtype=float)
for i, v in zip(idx, vol.values):
vols[i] += float(v)
centers = (edges[:-1] + edges[1:]) / 2.0
poc_i = int(np.argmax(vols)) if vols.sum() > 0 else bin_count // 2
poc = float(centers[poc_i])
# Value Area:从 POC 向两侧扩展直到累计 >= value_area_pct
total = float(vols.sum()) or 1.0
target = total * value_area_pct
left = right = poc_i
acc = float(vols[poc_i])
while acc < target and (left > 0 or right < bin_count - 1):
left_v = vols[left - 1] if left > 0 else -1.0
right_v = vols[right + 1] if right < bin_count - 1 else -1.0
if right_v >= left_v and right < bin_count - 1:
right += 1
acc += float(vols[right])
elif left > 0:
left -= 1
acc += float(vols[left])
else:
break
bins: List[Dict[str, float]] = [
{"price": float(centers[i]), "volume": float(vols[i])} for i in range(bin_count)
]
return {
"bins": bins,
"poc": poc,
"vah": float(centers[right]),
"val": float(centers[left]),
"bin_count": bin_count,
}
+2 -2
View File
@@ -467,7 +467,7 @@ class BiBuilderMixin:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False:
pre_last_bi.update_bi(klc)
#pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
#last_top.set_fx(Chan_FX_TYPE.PTOP)
@@ -585,7 +585,7 @@ class BiBuilderMixin:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False:
pre_last_bi.update_bi(klc)
#pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
last_bottom = klc
+171
View File
@@ -0,0 +1,171 @@
"""增量更新:新K只追加 KLU/KLC,笔与笔中枢在当前列表上重算。
不改 init_TF_DF 的整段语义。笔必须整表重扫:最后一笔 is_sure 允许收回
OWN_CHAN_ZS_001 上 60 天出现 7 次)。笔中枢用 cal_bi_zs_list_pure。
"""
from __future__ import annotations
from datetime import datetime
import pandas as pd
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_KLC_STATE
from chanlun.core.ChanKLU import ChanKLU
class IncrementalBuilderMixin:
def init_stream(self, df, interval=1, timeframe=None):
"""用历史K线初始化流式状态,之后用 append_bar / replace_last_bar。"""
if df is None or df.empty:
raise ValueError("DataFrame for stream is empty.")
if "date" not in df.columns:
raise ValueError(f"DataFrame missing 'date' column. Columns: {df.columns.tolist()}")
self.timeframe = timeframe
self.interval = interval
if interval == 1:
self.dataframe = df.copy()
else:
self.dataframe = resample_to_interval(df, interval)
self.dataframe = self.add_indicators(self.dataframe)
self.klu_list = []
self.klc_list = []
self.bi_list = []
self.bi_zs_list = []
self.seg_list = []
self.zs_list = []
self.bsp_list = []
self.klc_fx_list = []
self.big_zs_list = []
self._klc_feed_last_klu = None
for i in range(len(self.dataframe)):
self._append_row_at(i, rebuild=False)
self.rebuild_bi_zs()
return self
def append_bar(self, row):
"""追加一根已收盘K线。同一时间戳则改为替换最后一根。"""
self._ensure_stream_state()
item = self._normalize_row(row)
if self.klu_list and self.klu_list[-1].time == self._row_time_str(item):
return self.replace_last_bar(item)
self._append_item_to_dataframe(item)
self.dataframe = self.add_indicators(self.dataframe)
self._append_row_at(len(self.dataframe) - 1, rebuild=True)
return self
def replace_last_bar(self, row):
"""更新最后一根K(未完成K线走新OHLC)。包含关系从 KLU 列表重放。"""
self._ensure_stream_state()
if not self.klu_list:
return self.append_bar(row)
item = self._normalize_row(row)
idx = self.dataframe.index[-1]
for key, val in item.items():
self.dataframe.at[idx, key] = val
self.dataframe = self.add_indicators(self.dataframe)
self._apply_item_to_klu(self.klu_list[-1], self.dataframe.iloc[-1])
self._rebuild_klc_from_klu()
self.rebuild_bi_zs()
return self
def rebuild_bi_zs(self):
"""在当前 KLC 上重算笔 + cal_bi_zs_list_pure。会先清分型标记。"""
self._reset_klc_bi_marks(self.klc_list)
self.bi_list = self.cal_bi_list(self.klc_list) if self.klc_list else []
self.bi_zs_list = self.cal_bi_zs_list_pure(self.bi_list) if self.bi_list else []
return self.bi_zs_list
def _ensure_stream_state(self):
if not hasattr(self, "klu_list") or self.klu_list is None:
self.klu_list = []
if not hasattr(self, "klc_list") or self.klc_list is None:
self.klc_list = []
if not hasattr(self, "dataframe") or self.dataframe is None:
self.dataframe = DataFrame(
columns=["date", "open", "high", "low", "close", "volume"]
)
if not hasattr(self, "_klc_feed_last_klu"):
self._klc_feed_last_klu = self.klu_list[-1] if self.klu_list else None
if not hasattr(self, "bi_zs_list"):
self.bi_zs_list = []
def _rebuild_klc_from_klu(self):
self.klc_list = []
last_klu = None
for klu in self.klu_list:
self._push_klu_into_klc_list(self.klc_list, klu, last_klu)
last_klu = klu
self._klc_feed_last_klu = last_klu
def _append_row_at(self, idx, rebuild=True):
item = self.dataframe.iloc[idx]
klu = self._klu_from_item(item, idx)
if self.klu_list:
self.klu_list[-1].set_next(klu)
klu.set_pre(self.klu_list[-1])
self._push_klu_into_klc_list(self.klc_list, klu, self._klc_feed_last_klu)
self._klc_feed_last_klu = klu
self.klu_list.append(klu)
if rebuild:
self.rebuild_bi_zs()
def _klu_from_item(self, item, idx):
klu = ChanKLU(
self._item_time_str(item),
item["open"],
item["high"],
item["low"],
item["close"],
item["volume"],
)
klu.set_idx(idx)
if not hasattr(klu, "ema13"):
klu.ema13 = 0
if "macd" in item:
klu.set_indicators(item)
return klu
def _apply_item_to_klu(self, klu, item):
klu.time = self._item_time_str(item)
klu.open = item["open"]
klu.high = item["high"]
klu.low = item["low"]
klu.close = item["close"]
klu.volume = item["volume"]
klu.range = klu.high - klu.low
klu.body = abs(klu.close - klu.open)
if "macd" in item:
klu.set_indicators(item)
def _reset_klc_bi_marks(self, klc_list):
for klc in klc_list:
klc.fx = Chan_FX_TYPE.UNKNOWN
klc.klc_fx_type = Chan_KLC_FX.UNKNOWN
klc.klc_state = Chan_KLC_STATE.UNKNOWN
klc.bi = None
klc.fx_confirmed = False
def _item_time_str(self, item):
date = item["date"]
if hasattr(date, "to_pydatetime"):
date = date.to_pydatetime()
if isinstance(date, datetime):
return date.strftime("%Y-%m-%d %H:%M:%S")
return str(date)
def _row_time_str(self, item):
return self._item_time_str(item)
def _normalize_row(self, row):
if isinstance(row, pd.Series):
return row
return pd.Series(row)
def _append_item_to_dataframe(self, item):
row_df = DataFrame([item])
if self.dataframe is None or self.dataframe.empty:
self.dataframe = row_df
else:
self.dataframe = pd.concat([self.dataframe, row_df], ignore_index=True)
+2 -2
View File
@@ -55,8 +55,8 @@ class IndicatorsBuilderMixin:
return None
def add_indicators(self, df):
fast = 12
slow = 26
fast = 26
slow = 52
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
+55 -36
View File
@@ -86,7 +86,8 @@ class KlineBuilderMixin:
return Chan_FX_TYPE.UNKNOWN
def check_fx(self, klc):
if klc.pre and klc.next:
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
if klc.pre and klc.next and klc.next.end_klu is not None:
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
@@ -99,6 +100,21 @@ class KlineBuilderMixin:
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx3(self, klc):
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
if klc.pre and klc.next and klc.next.end_klu is not None:
next_klu = klc.next.end_klu.next
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low and klc.high > next_klu.high:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high and klc.low < next_klu.low:
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx2(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.close and klc.close > klc.next.close and klc.close > klc.pre.close and klc.close > klc.next.close:
@@ -171,6 +187,43 @@ class KlineBuilderMixin:
def get_kl_data(self, dataframe:DataFrame):
return self.cal_kl_data(dataframe)
def _push_klu_into_klc_list(self, klc_list, klu, last_klu):
"""把一根 KLU 并入包含K线列表。与 get_klc_list 的几何规则相同。"""
if len(klc_list) > 0:
last_klc = klc_list[-1]
if klu.exception:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc.high = klu.close if klu.close > klu.open else klu.open
klc.low = klu.open if klu.close > klu.open else klu.close
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
else:
included = last_klc.check_klu_included(klu)
if not included:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
else:
last_klc.add_klu(klu)
else:
ddir = Chan_KLINE_DIR.UP
if klu.open > klu.close:
ddir = Chan_KLINE_DIR.DOWN
klc = ChanKLC(klu, 0, ddir)
klc_list.append(klc)
def get_klc_list(self, klu_list):
klc_list = []
last_klu = None
@@ -198,41 +251,7 @@ class KlineBuilderMixin:
ema_down_list.append(ema_down_count)
#print(last_klu.time, ema_down_count, "DOWN END")
ema_down_count = 0
if len(klc_list) > 0:
last_klc = klc_list[-1]
if klu.exception:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc.high = klu.close if klu.close > klu.open else klu.open
klc.low = klu.open if klu.close > klu.open else klu.close
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
#print(klu.time, klu.high, klu.low, klu.close, klu.open, klu.exception)
else:
included = last_klc.check_klu_included(klu)
if not included:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
else:
last_klc.add_klu(klu)
else:
ddir = Chan_KLINE_DIR.UP
if klu.open > klu.close:
ddir = Chan_KLINE_DIR.DOWN
klc = ChanKLC(klu, 0, ddir)
klc_list.append(klc)
self._push_klu_into_klc_list(klc_list, klu, last_klu)
last_klu = klu
klc_list = self.cal_trend(klc_list)
#print(ema52_up_list, ema52_down_list)
+11 -6
View File
@@ -355,9 +355,12 @@ class ZsBuilderMixin:
return bi_zs_list
def get_zs_range(bis):
zg = min(bi.high for bi in bis)
zd = max(bi.low for bi in bis)
return zg, zd
bis_list = bis[0:3]
zg = min(bi.high for bi in bis_list)
zd = max(bi.low for bi in bis_list)
dd = min(bi.low for bi in bis_list)
gg = max(bi.high for bi in bis_list)
return zg, zd, dd, gg
def is_bi_overlap_range(bi, zg, zd):
return bi.high >= zd and bi.low <= zg
@@ -375,8 +378,8 @@ class ZsBuilderMixin:
zs.bi_list = list(bis)
for bi in zs.bi_list:
bi.set_bi_zs(zs)
zs.set_gg(max(bi.high for bi in zs.bi_list))
zs.set_dd(min(bi.low for bi in zs.bi_list))
#zs.set_gg(max(bi.high for bi in zs.bi_list))
#zs.set_dd(min(bi.low for bi in zs.bi_list))
zs.classify_zs()
last_zs = None
@@ -394,7 +397,7 @@ class ZsBuilderMixin:
start_idx += 1
continue
zg, zd = get_zs_range([bi1, bi2, bi3])
zg, zd, dd, gg = get_zs_range([bi1, bi2, bi3])
if zg <= zd:
start_idx += 1
continue
@@ -420,6 +423,8 @@ class ZsBuilderMixin:
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
zs.set_zg(zg)
zs.set_zd(zd)
zs.set_dd(dd)
zs.set_gg(gg)
set_zs_bi_list(zs, bis_for_zs)
zs.set_end_bi(bis_for_zs[-1], bis_for_zs[-1].sure_time)
+9
View File
@@ -178,6 +178,15 @@ class ChanLun():
def cal_bi_zs_list(self, bi_list):
#return self.tf_df.cal_bi_zs(bi_list)
return self.tf_df.cal_bi_zs_list(bi_list)
def cal_bi_zs_list_pure(self, bi_list):
return self.tf_df.cal_bi_zs_list_pure(bi_list)
def init_stream(self, dataframe, interval=1, timeframe=None):
self.tf_df.init_stream(dataframe, interval, timeframe)
return self.tf_df
def append_bar(self, row):
return self.tf_df.append_bar(row)
def replace_last_bar(self, row):
return self.tf_df.replace_last_bar(row)
def get_bi_zs_list(self, bi_list):
return self.tf_df.get_bi_zs_list(bi_list)
def get_decimal(self, value):
+4 -1
View File
@@ -31,12 +31,13 @@ from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
from chanlun.pipeline.builders.bi import BiBuilderMixin
from chanlun.pipeline.builders.bsp import BspBuilderMixin
from chanlun.pipeline.builders.incremental import IncrementalBuilderMixin
from chanlun.pipeline.builders.indicators import IndicatorsBuilderMixin
from chanlun.pipeline.builders.kline import KlineBuilderMixin
from chanlun.pipeline.builders.seg import SegBuilderMixin
from chanlun.pipeline.builders.zs import ZsBuilderMixin
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin):
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin, IncrementalBuilderMixin):
def __init__(self, df=None, interval=0, timeframe=None):
if df is not None:
self.init_TF_DF(df, interval, timeframe)
@@ -59,12 +60,14 @@ class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilde
self.klc_list = []
self.bi_list = []
self.zs_list = []
self.bi_zs_list = []
self.bsp_list = []
self.seg_list = []
self.klc_fx_list = []
self.klu_list = self.cal_kl_data(self.dataframe)
self.klc_list = self.get_klc_list(self.klu_list)
self.bi_list = self.cal_bi_list(self.klc_list)
self.bi_zs_list = self.cal_bi_zs_list_pure(self.bi_list)
self.seg_list = self.get_seg_list(self.bi_list)
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
self.big_zs_list = self.get_big_zs_list(self.zs_list)
+1
View File
@@ -0,0 +1 @@
from __future__ import annotations
+141
View File
@@ -0,0 +1,141 @@
from __future__ import annotations
import sys
import unittest
from pathlib import Path
import pandas as pd
_CHAN = Path(__file__).resolve().parents[2]
if str(_CHAN) not in sys.path:
sys.path.insert(0, str(_CHAN))
from chanlun.pipeline.timeframe import TF_DF # noqa: E402
def _zigzag_df(n=160, step=8):
dates = pd.date_range("2024-01-01", periods=n, freq="5min")
rows = []
price = 100.0
for i, date in enumerate(dates):
up = (i // step) % 2 == 0
if up:
o = price
c = price + 1.5
h = c + 0.3
l = o - 0.2
else:
o = price
c = price - 1.5
h = o + 0.2
l = c - 0.3
price = c
rows.append(
{
"date": date,
"open": o,
"high": h,
"low": l,
"close": c,
"volume": 1.0,
}
)
return pd.DataFrame(rows)
def _sure_bi_key(bi):
return (str(bi.start_time), bi.dir.name, round(float(bi.high), 6), round(float(bi.low), 6))
def _zs_key(zs):
return (
str(zs.start_time),
round(float(zs.zg), 6),
round(float(zs.zd), 6),
len(zs.bi_list),
)
class TestIncremental(unittest.TestCase):
def test_init_stream_matches_batch_push(self):
df = _zigzag_df()
stream = TF_DF()
stream.init_stream(df, 1, "5m")
batch = TF_DF()
indexed = batch.add_indicators(df.copy())
klu = batch.cal_kl_data(indexed)
klc = []
last = None
for k in klu:
batch._push_klu_into_klc_list(klc, k, last)
last = k
batch.klc_list = klc
batch.rebuild_bi_zs()
self.assertEqual(len(stream.klu_list), len(klu))
self.assertEqual(len(stream.klc_list), len(klc))
self.assertEqual(
[_sure_bi_key(b) for b in stream.bi_list if b.is_sure],
[_sure_bi_key(b) for b in batch.bi_list if b.is_sure],
)
self.assertEqual(
[_zs_key(z) for z in stream.bi_zs_list],
[_zs_key(z) for z in batch.bi_zs_list],
)
def test_append_bar_matches_init_stream(self):
df = _zigzag_df()
stream = TF_DF()
stream.init_stream(df, 1, "5m")
inc = TF_DF()
for _, row in df.iterrows():
inc.append_bar(row)
self.assertEqual(len(inc.klu_list), len(stream.klu_list))
self.assertEqual(len(inc.klc_list), len(stream.klc_list))
self.assertEqual(
[_sure_bi_key(b) for b in inc.bi_list if b.is_sure],
[_sure_bi_key(b) for b in stream.bi_list if b.is_sure],
)
self.assertEqual(
[_zs_key(z) for z in inc.bi_zs_list],
[_zs_key(z) for z in stream.bi_zs_list],
)
def test_replace_last_bar_keeps_count(self):
df = _zigzag_df(n=80)
tf = TF_DF()
tf.init_stream(df, 1, "5m")
n_klu = len(tf.klu_list)
last = df.iloc[-1].copy()
last["close"] = float(last["close"]) + 0.01
last["high"] = max(float(last["high"]), float(last["close"]))
tf.replace_last_bar(last)
self.assertEqual(len(tf.klu_list), n_klu)
self.assertGreater(len(tf.klc_list), 0)
def test_check_fx_skips_forming_right_wing(self):
from types import SimpleNamespace
from chanlun.core.ChanEnum import Chan_FX_TYPE
tf = TF_DF()
pre = SimpleNamespace(high=10, low=8)
nxt_open = SimpleNamespace(high=11, low=7, end_klu=None)
nxt_done = SimpleNamespace(high=11, low=7, end_klu=object())
center = SimpleNamespace(
pre=pre,
next=nxt_open,
high=12,
low=9,
set_fx=lambda *_a, **_k: None,
)
self.assertEqual(tf.check_fx(center), Chan_FX_TYPE.UNKNOWN)
center.next = nxt_done
self.assertEqual(tf.check_fx(center), Chan_FX_TYPE.TOP)
if __name__ == "__main__":
unittest.main()
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8882,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_perpetual.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "YOUR_BINANCE_API_KEY",
"secret": "YOUR_BINANCE_API_SECRET",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "YOUR_TELEGRAM_BOT_TOKEN",
"chat_id": "YOUR_TELEGRAM_CHAT_ID"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "change_me_to_a_random_secret_key",
"ws_token": "change_me_to_a_random_ws_token",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "BTC_Perpetual_Bot",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8800,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-89
View File
@@ -1,89 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_1m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {
"proxies": {
"http": "http://127.0.0.1:7897",
"https": "http://127.0.0.1:7897"
}
},
"ccxt_async_config": {
"aiohttp_proxy": "http://127.0.0.1:7897"
},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "8197349375:AAH208JghCq8raFYF-IpnobYknCr6iGDH_0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 1
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-68
View File
@@ -1,68 +0,0 @@
{
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_5m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 5,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {
"proxies": {
"http": "http://127.0.0.1:7897",
"https": "http://127.0.0.1:7897"
}
},
"ccxt_async_config": {
"aiohttp_proxy": "http://127.0.0.1:7897"
},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"internals": {
"process_throttle_secs": 5
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-87
View File
@@ -1,87 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_eth_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ETH/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": false,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": false,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-151
View File
@@ -1,151 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.95,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol_optimized.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 2,
"exit": 2,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {
"options": {"defaultType": "swap"}
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000,
"timeout": 30000
},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": []
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "error",
"jwt_secret_key": "",
"username": "",
"password": ""
},
"discord": {
"enabled": false,
"webhook": "",
"webhook_avatar": "",
"poll_delay_seconds": 10
},
"notification_settings": {
"status": "on",
"status_inactive_after": 7,
"timeframe_condition_change": "on",
"telegram": { },
"discord": { },
"notify_all": true
},
"bot_name": "SOL_Chan_Optimized",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.007,
"stoploss_range_step": 0.001,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"order_types": {
"entry": "limit",
"exit": "market",
"emergency_exit": "market",
"force_exit": "market",
"force_entry": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"strategy_path": "./user_data/Chan/strategies/",
"strategy": "ChanLun_SOL_Optimized",
"minimal_roi": {
"0": 0.012,
"120": 0.010,
"240": 0.007,
"360": 0.005
},
"stoploss": -0.007,
"trailing_stop": true,
"trailing_stop_positive": 0.003,
"trailing_stop_positive_offset": 0.005,
"trailing_only_offset_is_reached": true,
"use_custom_stoploss": true,
"max_open_trades_per_pair": 1,
"dry_run_wallet_refresh_time": 5,
"caches": {
"dataframe": {
"enabled": true,
"refresh_period": 60
},
"strategy": {
"enabled": true,
"refresh_period": 300
}
},
"pairlists": [
{
"method": "StaticPairList",
"config": {
"pairs": ["SOL/USDT:USDT"]
}
}
]
}
-125
View File
@@ -1,125 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"freqai": {
"enabled": true,
"purge_old_models": true,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier": "chanLun1",
"live_retrain_hours": 1,
"expiration_hours": 48,
"fit_live_predictions_candles": 0,
"data_kitchen_thread_count": 4,
"save_backtest_models": true,
"save_metadata": true,
"feature_parameters": {
"include_timeframes": [
"1m",
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20, 30],
"allow_duplicate_train": true
},
"data_split_parameters": {
"test_size": 0.25
},
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"max_depth": 5,
"subsample": 0.8,
"colsample_bytree": 0.8,
"use_label_for_weight": true,
"booster": "gbtree",
"num_class": 2
}
},
"freqaimodel": "XGBoostClassifier",
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-103
View File
@@ -1,103 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "30m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "ocQUqAPSD9PDhIL2lTMlMan0wMFwvvu5Fv8eYF3wUM8yPytm2jBgz51cgiHXw7J6",
"secret": "yHIc6FOnSoOI2FvygpRKKku4FKaZGI5DSwC83Ip4wRfUcxszennF6hy2vhbVuLYJ",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
"SUI/USDT:USDT",
"1INCH/USDT:USDT",
"DOGE/USDT:USDT",
"TON/USDT:USDT",
"UNI/USDT:USDT",
"XRP/USDT:USDT",
"SUN/USDT:USDT",
"NOT/USDT:USDT",
"RARE/USDT:USDT",
"RDNT/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "VolumePairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"strategy": "ChanStrategy",
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDC",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "hyperliquid",
"walletAddress": "0xA834b6d3Fa1D8A55ea8e502685ef5cbD2b2D3343",
"privateKey": "0xa399cea4c01be67c16b88e1d2121ed6e72bab6b4e8d81684ed03ce78f8b4f827",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"PURR/USDC:USDC",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan_sol_30.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8801,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-93
View File
@@ -1,93 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"db_url": "sqlite:///tradesv3.deepseek_trader.sqlite",
"dry_run": true,
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8001,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": true,
"internals": {
"process_throttle_secs": 15
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema26_ema52_cross.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema_pattern.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1h",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-70
View File
@@ -1,70 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.elliottwave_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 3,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "freqtrade_secret",
"ws_token": "freqtrade_ws",
"username": "freqtrade",
"password": "freqtrade"
},
"bot_name": "ElliottWaveBTC"
}
-123
View File
@@ -1,123 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.freqai_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList"
}
],
"freqai": {
"enabled": true,
"purge_old_models": 2,
"train_period_days": 10,
"backtest_period_days": 7,
"live_retrain_hours": 1,
"identifier": "sol_futures_lgbm_v1",
"feature_parameters": {
"include_timeframes": [
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT"
],
"label_period_candles": 12,
"include_shifted_candles": 1,
"DI_threshold": 0.9,
"weight_factor": 0.9,
"principal_component_analysis": false,
"use_SVM_to_remove_outliers": true,
"indicator_periods_candles": [
14
],
"plot_feature_importances": 0
},
"data_split_parameters": {
"test_size": 0.15,
"random_state": 42
},
"model_training_parameters": {
"n_estimators": 300,
"learning_rate": 0.05,
"max_depth": 5,
"num_leaves": 31,
"min_child_samples": 20,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 0.1,
"n_jobs": 1,
"verbosity": -1
}
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqai_sol",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.heikinashi_btc.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema26_ema52_cross.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"timeframe": "1m",
"can_short" : true,
"process_only_new_candles" : true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8821,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
-81
View File
@@ -1,81 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.sol5m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "SOL5m",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
-83
View File
@@ -1,83 +0,0 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "15m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"WIF/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
Binary file not shown.
-82
View File
@@ -1,82 +0,0 @@
#!/bin/bash
# 缠论分析系统生产环境部署脚本
# 显示执行的命令
set -x
# 确保脚本在错误时停止
set -e
# 项目根目录
PROJECT_DIR=$(pwd)
echo "项目将部署在: $PROJECT_DIR"
# 创建虚拟环境
echo "创建Python虚拟环境..."
python3 -m venv venv
source venv/bin/activate
# 安装依赖
echo "安装依赖包..."
pip install --upgrade pip
pip install flask pandas matplotlib ccxt pytz talib-binary gunicorn
# 安装任何额外的系统依赖
# sudo apt-get update
# sudo apt-get install -y python3-dev
# 检查目录结构
echo "检查目录结构..."
mkdir -p web/templates
# 创建日志目录
mkdir -p logs
# 创建启动脚本
echo "创建启动脚本..."
cat > start_service.sh << 'EOF'
#!/bin/bash
# 缠论分析系统启动脚本
# 项目根目录
PROJECT_DIR=$(pwd)
cd $PROJECT_DIR
# 激活虚拟环境
source venv/bin/activate
# 启动服务
cd web
echo "启动缠论分析系统服务..."
gunicorn app:app --bind=0.0.0.0:8123 --workers=4 --timeout=120 --log-level=info --log-file=logs/chanlun.log --daemon
echo "服务已启动,端口8123"
echo "日志文件位置: $PROJECT_DIR/web/logs/chanlun.log"
EOF
# 创建停止脚本
echo "创建停止脚本..."
cat > stop_service.sh << 'EOF'
#!/bin/bash
# 停止缠论分析系统服务
echo "停止缠论分析系统服务..."
pkill -f "gunicorn app:app"
echo "服务已停止"
EOF
# 添加执行权限
chmod +x start_service.sh
chmod +x stop_service.sh
# 修改app.py中的调试模式(生产环境应关闭调试模式)
if [ -f web/app.py ]; then
echo "配置app.py为生产环境模式..."
sed -i 's/app.run(debug=True, host='\''0.0.0.0'\'', port=8123)/# 在生产环境中,使用gunicorn启动服务\n# app.run(debug=False, host='\''0.0.0.0'\'', port=8124)/' user_data/Chan/web/app.py
else
echo "警告: 未找到app.py文件"
fi
echo "部署完成!"
echo "使用 ./start_service.sh 启动服务"
echo "使用 ./stop_service.sh 停止服务"
-22
View File
@@ -1,22 +0,0 @@
# ADR-001: 包布局与兼容 shim
**Status:** Accepted
**Date:** 2026-08-05
**ECR:** ECR-001
## Context
根目录扁平模块被 strategies 与 web 通过模块名直接 import;完全改名会破坏 Freqtrade 策略。需要正式包边界,同时零改 `strategies/`
## Decision
1. 正式包名:`chanlun``core` / `pipeline` / `indicators` / `analysis`)。
2. 根目录保留同名 shim 文件,再导出公共符号。
3. Web 使用 Flask blueprints + services;前端 JS 模块化,不引入 TS 构建。
4. `TF_DF` 保留门面类名与公开方法,内部委托 builders。
## Consequences
- 策略无需修改。
- 长期可逐步引导新代码 `from chanlun import ...`
- shim 需保持至策略侧显式迁移(另立 ECR)。
-43
View File
@@ -1,43 +0,0 @@
# AGENT_MEMORY — chan
> Agent 短记忆。先读 `PROJECT_PROFILE.md`,再读本文件。不要把猜测写进这里。
## 双前端
| 入口 | 引擎 | 实时 |
|------|------|------|
| `/` | Lightweight Charts | HTTP 定时自动刷新(增量 + 每 6 次全量) |
| `/chan_tv` | Charting Library 全版 | datafeed `subscribeBars` → WS |
勿把主站 `live_feed` 方案与 chan_tv datafeed 混为一谈;主站 WS 实时已回退。
## 版本
- `system_version``v1.0.0`ECR-001
- `strategy_version`:与 system 解耦;默认不改 `config/` / `strategies/`
## 近期变更
- IDEA-002 / `9f1e736`:主站内存泄漏 dispose、首屏单次 analyze、ChanMACD 复用、chan_tv 体验
- ECR-002 Reviewed:拆 `web/services/runtime/`、加深 analyze 契约
- ECR-003 Reviewed:主站威科夫叠层(`chanlun/analysis/wyckoff/` + `include_wyckoff`)→ `081a57a`
- ECR-004 ReviewedTR 评分硬化 + VP 少系列 + 阶段/门闩/单测(无币种参数)
- ECR-007 Final Approval / `276481e`Wyckoff Live Structure`live.py`);Confirmed ≠ Liveexecution 仅 confirmed
- 威科夫数据随主 analyze 默认返回;UI 开关仅显隐叠层
- Live 观察:主图左下角 Cycle Summary(「形成中」= FORMING);无单独 Live 图层
## 硬约束提醒
- `/api/analyze` 字段可增不可删
- 无 ADR 不改笔/段/中枢/买卖点语义
- 威科夫为独立叠层(ECR-003/007);勿借机改缠论算法
- Live candidate **不得**进入 execution;交易 L2+ → RISK_REVIEW + EXPLive 须 Human
## 已知债务
- `chart_tv.js` 单体巨大 → 后续可选 ECR
- analyze 契约已加深(mock HTTP + wyckoff opt-in);可再加固定 JSON 快照文件
- 内存泄漏尚无自动化 heap/监听断言
- `macd_config` POST 写本地 global 的历史 quirks(未改)
- 威科夫启发式参数未做 UI 调参
- ECR-007 待 PR 合入 `dev`
@@ -1,70 +0,0 @@
# Backend Design: ECR-007 Wyckoff Live Structure
| Field | Value |
|-------|-------|
| ID | BD-2026-007 |
| ECR | ECR-007 |
| Change Level | L2 |
| Status | Approved |
| Author | Architect (LOOP-RUN-005 Planner) |
| Date | 2026-08-07 |
| Risk | High (domain / execution boundary) |
---
## Context
- 问题:Confirmed 引擎已存在;需要独立 Live 推演层供观察,且不得成为交易执行输入。
- 非目标:改 Confirmed 门槛;自动交易;策略。
- 依赖:ECR-003/004 威科夫;WYCKOFF-LIVE-STRUCTURE-001FROZEN)。
## Architecture Change / Change Boundary
```text
OHLCV
→ detect_trading_ranges (Confirmed path)
→ detect_bias_and_events / build_phases ← Confirmed(阈值不降)
→ analyze_live_structure ← Live(只读 confirmed
→ cycles[i] = { lifecycle, confirmed, live }
→ API analyze + Summary UI
→ execution_signal_from_wyckoff(confirmed only)
```
| Layer | May change | Must not |
|-------|------------|----------|
| Confirmed | assemble into `confirmed{}` | relax Spring/SOS rules |
| Live | `live.py` heuristics | write into confirmed.events |
| Execution helper | source=confirmed gate | consume candidates |
| UI | Summary partition | treat Live as order |
## Backend Change Boundary
Live outputs are **observation**. Execution boundary:
```python
assert execution_signal.source == "confirmed"
# live-only payload → None
```
## Data contract
See WYCKOFF-LIVE-STRUCTURE-001. Top-level `phases`/`events` mirror **Confirmed** only.
## delivery_constraints
- BD Status Approved
- TEST_REPORT commands/result/date
- CODE_REVIEW handoff
- TRACEABILITY commit
- out_of_scope + execution_source_confirmed_only
## Test Plan
1. Live candidates not in confirmed.events
2. CONFIRMED lifecycle when Spring+SOS confirmed
3. execution_signal source=confirmed; live-only → None
4. analyze contract keys include live/lifecycle
## Rollback
Remove live assembly path; Summary falls back to confirmed-only.
-83
View File
@@ -1,83 +0,0 @@
# CHANGELOG
## Unreleased — 2026-08-07
### ECR-007L2LOOP-RUN-005
- Wyckoff **Live Structure**`live.py` + engine 组装 `lifecycle` / `confirmed` / `live`
- Event candidatesSpring/SOS/LPS/UTAD+ 可解释 confidenceSummary Confirmed/Live 分区
- `execution_signal_from_wyckoff` **仅** `source=confirmed`Live-only → None
- **No** Confirmed 门槛降低;**No** strategies / 自动交易
## Unreleased — 2026-08-06
### ECR-004L2Reviewed
- 威科夫 TR 评分选段(防吞前置趋势);阶段非重叠最小跨度
- 主站 VP Top-8 + bins≤24;填充线减负
- `elements_only` 时不跑威科夫;收紧单测(无币种独立参数)
- **后续**:威科夫随主 `/api/analyze` 默认一并返回;前端开关只控制绘制(不再勾选才加载)
### ECR-003L2Reviewed
- 新增 `chanlun/analysis/wyckoff/`:交易区间、阶段 AE、Spring/SOS/LPS/UTAD 等事件、区间 VPPOC/VAH/VAL)、量能确认
- `/api/analyze` 按需 `include_wyckoff=1` 返回顶层 `wyckoff`
- 主站「威科夫」开关与 Lightweight 叠层(区间/阶段/事件/VP
- 单测与 analyze 契约 opt-in 断言
### ECR-002L3Reviewed
- 拆分 `web/services/runtime.py` 为包 `web/services/runtime/`state / timeframes / market_data / indicators / analyze / serialize
- 加深 analyze 契约测试(mock HTTP + analyze_chan 键集 + serialize JSON
- 新增 TF_DF 全量 init 冒烟与 runtime 门面测试
### IDEA-002L1 补档)
对应 commit `9f1e736`。无新 system tag(仍为 `v1.0.0`)。
#### Fixed
- 主站自动刷新内存泄漏:`disposeTradingViewCharts`、去掉重复 sync 监听、默认增量刷新(每 6 次全量重建笔/段/中枢)
- 加密货币首屏重复调用 `/api/analyze`
- ChanMACD 同周期重复全量分析(复用 `get_klc_list` 结果)
#### Changed
- `/chan_tv`:WS/REST 可分离配置、指标布局 localStorage、未完成中枢与 datafeed 实时 tick 行为完善
- `PROJECT_PROFILE` Realtime 条目与 chan_tv WS 对齐(文档)
#### Docs
- ESSIDEA-002、AGENT_MEMORY、AGENTSECR-002 实现与报告
---
## v1.0.0 — 2026-08-05(首个正式 Release
对应 ECR-001 / tag `v1.0.0`。详见 `docs/RELEASE/ECR-001-v1.0.0.md`
### Added
- 正式包 `chanlun/`core / pipeline / builders / indicators / analysis
- ESS 文档树 `docs/`PROFILE / ECR / SPEC / ADR / HANDOFF / CODE_REVIEW / RELEASE
- Web `config.py``services/``api/` blueprints
- 前端 `web/static/js/app/` 模块
- Golden 回归 `tests/generate_golden.py` + fixtures
### Changed
- 根目录 `Chan*.py` / `TF_DF.py` 改为兼容 shim
- `TF_DF` 实现拆至 builders,门面签名保持
- `web/app.py` 瘦身为 `create_app()`
- `index.html` 去掉巨型 inline 业务 JS
- HTTP 代理改为环境变量配置
- strategies / web / tests / examples → `from chanlun...` 导入
### Fixed
- 恢复缺失的 `TF_DF.get_zs_list`(委托 `get_seg_zs_list`
### Moved
- 示例 → `examples/`;笔记 → `docs/notes/`
- 未接线 TSX/TS → `static/js/_unused/`
-75
View File
@@ -1,75 +0,0 @@
# CODE_REVIEW — ECR-001
**Role:** REVIEWER
**Date:** 2026-08-05
**Commit:** `74dec4e` (`refactor: 缠论引擎包化与 Web 分层(ECR-001`)
**Decision:** Approve
## Evidence loaded
- `docs/ECR/ECR-001-chan-web-restructure.md`
- `docs/ENGINEERING_SPEC/ECR-001-restructure.md`
- `docs/IMPLEMENTATION_REPORT/ECR-001.md`
- `docs/TEST_REPORT/ECR-001.md`
- `docs/HANDOFF/ECR-001-engineer-to-reviewer.md`
- Diff `e2e45bc..74dec4e`;本地复跑测试
## Acceptance ↔ Evidence
| Acceptance | Verdict | Evidence |
|------------|---------|----------|
| `from ChanLun import ChanLun` / `ChanEnum` 仍可用 | PASS | 复跑 shim+package 同一对象;`test_compat_shim_still_works` |
| Golden bi/seg/zs/bsp 与基线一致 | PASS | `python tests/generate_golden.py --check` → GOLDEN OKpytest 含 golden |
| `/api/analyze` 关键字段兼容 | PASS | `analyze_contract_keys.json` + 路由注册冒烟(未做实盘拉行情 E2E,见 Findings |
| `web/app.py` 瘦身 factory | PASS | `web/app.py` 32 行;`create_app` + blueprints |
| `index.html` 无大体量 inline 业务 JS | PASS | ~1482 行;业务在 `static/js/app/*` |
| ESS docs / TEST / IMPL / CHANGELOG | PASS | `docs/` 齐全 |
| `config/` 无内容变更 | PASS | `git diff e2e45bc..HEAD -- config` 空 |
| `strategies/` 无内容变更 | **AMENDED** | 见下「范围修订」 |
## 范围修订(Human 后续指示)
原 ECR Forbidden 写「不改 strategies/」。实现后期 Human 要求「一次性做完」导入迁移:strategies 仅改 import / `sys.path`26 files, +75/75),**无策略交易逻辑变更**。
审阅结论:视为 **L3 结构收尾的允许增补**,不构成交易语义 L2;建议 ECR Acceptance 改为「strategies 仅允许 import/path 迁移,禁止改买卖逻辑」。
**不据此 Request changes。**
## 复跑结果(Reviewer
```text
shim+package OK
GOLDEN OK {klu:400, klc:208, bi:14, seg:2, zs:0, bsp:3}
pytest tests/test_golden_pipeline.py web/tests/test_analyze_contract.py → 6 passed
```
## Findings
### Non-blocking(记入债务,需新 ECR 再动)
1. **`web/services/runtime.py` ~1176 行** — 已从 app 抽出但仍是大模块;facade 再导出符合计划 → **已起草 `docs/ECR/ECR-002-runtime-split.md`Draft**
2. **`web/static/js/app/chart_tv.js` ~4664 行** — `initTradingView` 单体;行为冻结下可接受;ECR-002 可选范围。
3. **`/api/analyze` 契约测试偏浅** — 仅关键字段清单 + 路由存在;无固定 fixture 的端到端 JSON 快照(需 mock 行情)→ ECR-002。
4. **TEST_REPORT 写「5 passed」** — 现为 6(含 shim 兼容测);Release 前可改正文(L0 docs)。
5. **L1`TF_DF.get_zs_list` 恢复** — 合理兼容修复;golden 走 analyze 路径未覆盖 `TF_DF(df,...)` 全量 `__init__` → ECR-002 Acceptance。
### No blockers
未发现违反「算法语义冻结 / API 可增不可删 / 无 Vite-React / config 未改」的证据。
## Decision
**Approve**
- ECR-001 可进入 Release(本变更无交易 EXP 门禁)。
- 非阻断项进入 backlog / 未来 ECR,不阻塞 tag。
## Next owner
`release_manager` — 写 RELEASE_REPORT、打 tag(需 Human 确认发布动作)。
## Traceability
| Item | Updated |
|------|---------|
| Acceptance mapping | 本文件 |
| STATE.owner | → release_manager |
| ECR Status | → Done (Reviewed) |
-74
View File
@@ -1,74 +0,0 @@
# CODE_REVIEW — ECR-002
**Role:** REVIEWER
**Date:** 2026-08-06
**Scope:** 工作区未提交实现(相对 `HEAD`/`9f1e736`);包 `web/services/runtime/` + 测试 + ESS 文档
**Decision:** Approve
## Evidence loaded
- `docs/ECR/ECR-002-runtime-split.md`
- `docs/ENGINEERING_SPEC/ECR-002-runtime-split.md`
- `docs/IMPLEMENTATION_REPORT/ECR-002.md`
- `docs/TEST_REPORT/ECR-002.md`
- `docs/HANDOFF/ECR-002-engineer-to-reviewer.md`
- 包源码:`web/services/runtime/{__init__,state,timeframes,market_data,indicators,analyze,serialize}.py`
- Diff:删除 `web/services/runtime.py`;新增包与测试
## Acceptance ↔ Evidence
| Acceptance | Verdict | Evidence |
|------------|---------|----------|
| runtime 门面公开符号兼容(含历史 `import *` 漏出) | PASS | 手工核对 api 所需符号;`timezone`/`OrderedDict`/`np`/`StructureZone*`/`ThreadPoolExecutor` 等在门面;`test_runtime_facade` |
| Golden 通过 | PASS | 复跑 `tests/test_golden_pipeline.py` |
| Analyze 契约加深 | PASS | `test_analyze_contract`:键清单 + analyze_chan 键集 + serialize JSON + mock HTTP |
| TF_DF 全量 init 冒烟 | PASS | `tests/test_tf_df_init.py``interval=1` |
| config/strategies 无交易逻辑 diff | PASS | 工作区无 `config/`/`strategies/` 变更 |
| IMPL / TEST / CHANGELOG / TRACEABILITY | PASS | docs 已落盘 |
| CODE_REVIEW Approve | PASS | 本文件 |
## 复跑结果(Reviewer
```text
PYTHONPATH=.:web python -m pytest \
tests/test_golden_pipeline.py \
tests/test_tf_df_init.py \
web/tests/test_runtime_facade.py \
web/tests/test_analyze_contract.py -q
→ 13 passed
```
算法冻结抽查:`analyze.py` 仍为 `cal_bi_zs(seg_list)` + `_last_chan_macd` 复用;未改笔段中枢语义。
## Findings
### Non-blocking(不挡 Approve
1. **门面标量同步只做一次**`__init__` 在首次 `refresh` 后把 `DATA_SERVICE_AVAILABLE` / `macd_*` 写入模块 dict;之后 `refresh_data_service_metadata` 只改 `state.*`。通过 `R.DATA_SERVICE_AVAILABLE` 读取可能与 state 短期不一致;`from services.runtime import *` 的 bool 拷贝问题在 monolith 时代已存在。建议后续 L1:在 `refresh` 末尾同步写回门面模块,或让标量只经 `state`/`__getattr__` 暴露。
2. **`__getattr__` 对已绑定名无效** — 与上条相关;属清理项。
3. **`chart_tv.js` 拆分未做** — ECR 明确可选;继续记入 backlog。
4. **契约测试仍无「固定 JSON 快照文件」** — 已有 mock HTTP + 键集,比 ECR-001 深;完整响应快照可另开 L1/ECR。
5. **`web/tests/test_cn_stock_data_fetch.py` 仍因旧 `user_data.Chan...` 路径无法收集** — 既有问题,非本 ECR 引入。
### No blockers
未发现违反「算法语义冻结 / API 可增不可删 / 无 Vite-React / 未动 strategies·config / 未引主站 WS」的证据。
## Decision
**Approve**
- ECR-002 可标 DoneReviewed);不强制新 system tag(仍为 `v1.0.0` Unreleased 文档变更)。
- 非阻断项进 backlog;不阻塞合并本实现。
## Next owner
`engineer` / Human — 提交合并;若要发版再交 `release_manager`(本 ECR 未要求 bump tag)。
## Traceability
| Item | Updated |
|------|---------|
| Acceptance mapping | 本文件 |
| STATE.owner | → idle / merge |
| ECR Status | → Done (Reviewed) |
-77
View File
@@ -1,77 +0,0 @@
# CODE_REVIEW — ECR-003
**Role:** REVIEWER
**Date:** 2026-08-06
**Scope:** 工作区未提交 ECR-003(相对 `origin/dev` @ `df27b4d`
**Decision:** Approve(带非阻断 Findings;建议合并前勿提交 `.DS_Store`
## Evidence loaded
- `chanlun/analysis/wyckoff/{engine,range,events,volume_profile}.py`
- `web/api/analyze.py``include_wyckoff`
- `web/templates/index.html``chart_view.js``macd_ui.js``chart_tv.js` 威科夫块
- `tests/test_wyckoff.py``web/tests/test_analyze_contract.py`
- ESSECR/PRODUCT/ENG/IMPL/TEST/HANDOFF
## Acceptance ↔ Evidence
| Acceptance | Verdict | Evidence |
|------------|---------|----------|
| `include_wyckoff=1` 返回约定键;默认不强制 | PASS | 契约测试;默认无 `wyckoff` 键 |
| 合成 TR + 事件;VP POC | PASS | `test_wyckoff.py`12 相关套件全绿) |
| 主站可开关绘制 | PASS | 主开关按需拉取;子项本地重绘 |
| golden 不变 | PASS | `test_golden_pipeline` |
| 未改缠论算法 / strategies / chan_tv | PASS | diff 范围核对 |
| ESS 闭环 | PASS | IMPL/TEST/TRACE/CHANGELOG/本文件 |
## 复跑
```text
PYTHONPATH=.:web python -m pytest \
tests/test_wyckoff.py tests/test_golden_pipeline.py \
web/tests/test_analyze_contract.py -q
→ 12 passed
```
## Findings
### Important(不挡 Approve,建议跟进)
1. **交易区间易吞并前置趋势**
`detect_trading_range` 从最长窗口向下搜,合成夹具下 `abs_start_idx=0`,箱体前下跌段被算进 TR。单测只断言「有区间 + 有事件」,未锁定高低/起点。
*建议:* 用「宽度/触边密度」评分取最优段,或要求近端触边;测试断言 `high≈60/low≈40` 与起点靠近箱体。
2. **VP 叠层系列数偏多,可能加压自动刷新内存**
开启 VP 时约每个 bin 一条 `addLineSeries`(默认 ~50),再加区间填充/阶段。与 IDEA-002 内存修复同路径全量重建时放大。
*建议:* 只画非零 bin 或合并为少量 series / histogram;或限制 `vp_bins` 上限到 24。
### Medium
3. **阶段 C–E 在事件扎堆时常退化重叠**
夹具输出中 D/E 起止几乎相同;状态机按事件锚点硬切,缺少最小阶段长度。展示可用,语义偏弱。
4. **`elements_only=true` 仍可能跑威科夫**
威科夫挂在路由末尾,不依赖 `not elements_only`。主站当前不这么发,但契约上奇怪;建议与主周期分析同门闩。
5. **单测断言偏松**
`Spring in types or SOS``abs(poc-50)<2` 对回归保护不足。
### Low
6. 失败时 `wyckoff.error` 回传异常字符串(与结构区 print 风格一致,信息暴露轻微)。
7. 事件 marker 一律 `arrowUp`(跌破类也可 `arrowDown`)。
8. 工作区 `.DS_Store` 脏文件——**勿纳入 commit**。
### No blockers
未发现:契约删键、缠论语义改动、策略/config 改动、未鉴权危险写操作、主站误引 WS。
## Decision
**Approve**
可合并提交(排除 `.DS_Store`)。Important #1/#2 可开后续 L1/L2,不阻塞本 ECR 着陆。
## Next owner
`engineer` / Human — commit(勿含 `.DS_Store`);可选跟进 TR 评分与 VP 绘图优化。
-53
View File
@@ -1,53 +0,0 @@
# CODE_REVIEW — ECR-004
**Role:** REVIEWER
**Date:** 2026-08-06
**Scope:** `d3188ca`(相对 ECR-003)威科夫硬化
**Decision:** Approve
## Evidence loaded
- Diff `d3188ca``range.py` / `events.py` / `analyze.py` / `chart_tv.js` / tests / ESS
- 复跑:`tests/test_wyckoff.py` + golden + analyze contract → **14 passed**
- 合成夹具抽查:`abs_start_idx=20`low/high≈40.1/59.9(相对 003 的 bar0 已修好)
## Acceptance ↔ Evidence
| Acceptance | Verdict | Evidence |
|------------|---------|----------|
| TR 不吞明显前置趋势;边界近箱体 | PASS | 评分选段;单测 low/high 带 + `abs_start≥12` + start 时间容差 |
| VP series 减负 | PASS | Top-8 + 填充 3 + POC/VAH/VALAPI bins≤24 |
| 阶段最小跨度 / 不重合 | PASS | 链式 cursorunique (start,end) 断言 |
| elements_only 门闩 | PASS | `include_wyckoff and not elements_only` + 契约测试 |
| golden 不变 / 无策略改动 / 无币种表 | PASS | golden 绿;diff 无 config/strategies |
## Findings
### Medium(不挡 Approve
1. **同分 tie-break 偏向更长窗口**
循环从长到短,`score <= best_score` 时保留已有(更长)。多数情况分数拉开;若实盘出现「长窗与短窗同分」,仍可能略偏长。可选:同分取更短,或加 `1/length` 微项。
2. **阶段常截断为 AC**
Spring/SOS 落在尾部时 D/E 因 `min_span` 被吃掉——与 ENG「空间不足截断」一致,但 UI 勾选「阶段」时用户可能期望总见 D/E。属产品预期,非缺陷;可在 UI/文档标明「尾部不足则省略」。
### Low
3. **`abs_start_idx >= 12` 弱于「箱体起点」** —— 主测已用时间容差;该断言可再收紧到 `>= 16` 一类。
4. **VP Top-N 无自动化 series 计数** —— 靠代码审查 + ENG 约定。
5. 事件 marker 仍一律 `arrowUp`003 遗留)。
6. 失败路径仍回传 `wyckoff.error` 字符串。
### No blockers
未发现契约删键、缠论语义改动、策略改动、或回归红灯。
## Decision
**Approve**
ECR-004 可维持 Done (Reviewed)。Medium 项进 backlog,不必立刻新 ECR,除非实盘 TR 仍偏长。
## Next owner
Human — 主站 BTC 勾选威科夫目测;无发版要求则保持 `v1.0.0` Unreleased 累计。
-70
View File
@@ -1,70 +0,0 @@
# ECR-001
**Title:** 缠论引擎包化 + Web 分层重构(行为冻结)
**Status:** Approved
**Date:** 2026-08-05
**Change Level:** L3
## Change
将根目录扁平 `Chan*.py` / `TF_DF.py` 包化为 `chanlun/`,拆分 `web/app.py` 与巨型 `index.html` inline JS;算法与 `/api/analyze` 契约冻结;`config/` / `strategies/` 不动。
## Motivation
根目录与 Web 单体过大、职责混杂,难以维护与测试;需在不影响 Freqtrade 策略导入的前提下重整结构。
## Scope
### Allowed
- 创建 `chanlun/`core / pipeline / indicators / analysis)与根目录兼容 shim
- 拆分 `TF_DF` 为 builders + 门面(公开方法签名不变)
- Web`config` + `services` + `api` blueprints;配置外置(proxy / DATA_SERVICE
- 前端:`index.html` 业务 JS 外置到 `static/js/app/`;隔离未接线 TS/TSX
- 示例与笔记迁入 `examples/` / `docs/notes/`
- Golden / 契约回归测试
### Forbidden
- 修改笔 / 线段 / 中枢 / 买卖点算法语义
- 破坏 `/api/analyze` JSON 字段(可增不可删)
- 修改 `config/`;修改 `strategies/` 交易逻辑或参数(import/`sys.path` 迁移除外,见 CODE_REVIEW
- 引入 Vite/React/TS 构建
- 重做 UI 视觉或更换 TradingView
## Risk
| Risk | Mitigation |
|------|------------|
| 策略 import 断裂 | 根 shim + 冒烟导入 |
| 拆文件改算法 | 仅搬移;golden fixture |
| 前端事件遗漏 | 按块抽取 + 手工/冒烟 |
| API 字段漂移 | analyze 契约测试 |
## Acceptance Criteria
- [x] `from ChanLun import ChanLun` / `from ChanEnum import ...` 仍可用
- [x] Golden:同一 fixture 下 bi/seg/zs/bsp 序列化结果与基线一致
- [x] `/api/analyze` 关键字段集合兼容(契约冒烟)
- [x] `web/app.py` 瘦身为 factory;业务在 services/api
- [x] `index.html` 不再含大体量业务 inline JS
- [x] ESS docs 齐全;TEST_REPORT / IMPLEMENTATION_REPORT / CHANGELOG
- [x] `config/` 无内容变更;`strategies/` 仅允许 import/`sys.path` 迁移(Human 增补,无交易逻辑变更)
**Status:** Done (Released as `v1.0.0`)
## Rollback
单分支 / 单 PR 回滚;shim 期可整体 `git revert`
## Risk Review
- Path: `docs/RISK_REVIEW/ECR-001.md` — N/A(不改交易语义)
## Linked
- PRD / PRODUCT_SPEC: `docs/PRODUCT_SPEC/ECR-001-restructure.md`
- ENGINEERING_SPEC: `docs/ENGINEERING_SPEC/ECR-001-restructure.md`
- ADR: `docs/ADR/ADR-001-package-layout.md`
- EXPERIMENT: N/A
- TRACEABILITY: Yes
-74
View File
@@ -1,74 +0,0 @@
# ECR-002
**Title:** 拆分 `web/services/runtime.py` + 加深 `/api/analyze` 契约测试
**Status:** Done (Reviewed)
**Date:** 2026-08-06
**Change Level:** L3(行为冻结;若 golden 漂移则升 L2)
## Change
将仍偏大的 `web/services/runtime.py` 按职责拆为可维护子模块;加深 analyze API 契约/快照测试;可选拆分主站巨型 `chart_tv.js`(本轮未做)。
## Motivation
ECR-001 CODE_REVIEW 非阻断债务:runtime 过大、契约测试偏浅、chart_tv 单体。不处理会继续抬高 Web 改动风险。
## Scope
### Allowed
- 物理拆分 `web/services/runtime.py` → 包 `web/services/runtime/`state / timeframes / market_data / indicators / analyze / serialize + 门面)
- 加深 `web/tests/`:固定 fixture / mock 行情下的关键字段快照与契约
-`TF_DF(..., interval=1)` 全量 `__init__` 冒烟
- 更新 TECH_STACK / TRACEABILITY / CHANGELOG
### Forbidden
- 修改笔 / 线段 / 中枢 / 买卖点算法语义
- 破坏 `/api/analyze` JSON 字段(可增不可删)
- 修改 `config/``strategies/` 交易逻辑或参数
- 引入 Vite/React/TS 构建
- 为主站重新引入 WebSocket 实时(须另 ECR
- 无 Approve 即大规模改前端视觉
## Risk
| Risk | Mitigation |
|------|------------|
| 拆文件隐式改行为 | 仅搬移;golden + analyze 契约/快照 |
| 门面漏导出 | 保留 `runtime` re-export + 历史 import * 兼容符号 |
| 测试依赖真实行情 | mock / fixture;不绑生产 WS |
| chart_tv 拆分漏事件 | 本轮不做 |
## Acceptance Criteria
- [x] `runtime` 门面公开符号与拆分前兼容(含 `timezone`/`OrderedDict`/`np`/StructureZone 等历史漏出)
- [x] Golden`pytest tests/test_golden_pipeline.py` 通过
- [x] Analyze 契约/快照测试通过且覆盖关键字段清单以上
- [x] TF_DF 全量 init 冒烟通过
- [x] `config/` / `strategies/` 无交易逻辑 diff
- [x] IMPLEMENTATION_REPORT / TEST_REPORT / CHANGELOG / TRACEABILITY 更新
- [x] CODE_REVIEW Approve
## Rollback
`git revert` 本 ECR 提交;门面保留期可整包回滚。
## Risk Review
- Path: `docs/RISK_REVIEW/ECR-002.md` — N/A(不改交易决策语义)
## Linked
- IDEA: `docs/IDEA/IDEA-003-runtime-split.md`
- PRODUCT_SPEC: `docs/PRODUCT_SPEC/ECR-002-runtime-split.md`
- ENGINEERING_SPEC: `docs/ENGINEERING_SPEC/ECR-002-runtime-split.md`
- ADR: 引用 ADR-001(包内再拆,无新顶层布局 ADR)
- EXPERIMENT: N/A
- TRACEABILITY: Yes
- IMPLEMENTATION_REPORT: `docs/IMPLEMENTATION_REPORT/ECR-002.md`
- TEST_REPORT: `docs/TEST_REPORT/ECR-002.md`
## Origin
- `docs/CODE_REVIEW/ECR-001.md` Findings 13、5
-62
View File
@@ -1,62 +0,0 @@
# ECR-003
**Title:** 主站威科夫分析与图表展示
**Status:** Done (Reviewed)
**Date:** 2026-08-06
**Change Level:** L2
## Change
在主站 `/` 增加威科夫交易区间、阶段(A–E)、关键事件(Spring/SOS/LPS/UTAD 等)、区间内简易 VPPOC/VAH/VAL)与量能确认;按需接入 `/api/analyze`
## Motivation
用户需要在缠论图上叠加威科夫结构解读;与现有结构区语义分离。
## Scope
### Allowed
- 新建 `chanlun/analysis/wyckoff/`
- `/api/analyze` 增加可选 `include_wyckoff` 与响应字段 `wyckoff`(可增不可删既有字段)
- 主站 UI 开关与 Lightweight 绘图
- 单测 + ESS 文档
### Forbidden
- 修改笔/段/中枢/买卖点算法语义
-`config/` / `strategies/`
- `/chan_tv` Study
- Vite/React、主站 WebSocket 实时(另 ECR
## Risk
| Risk | Mitigation |
|------|------------|
| 启发式误标 | 规格写明启发式;UI 可关;单测合成形态 |
| 负载 | 默认关闭,勾选才计算 |
| 与结构区混淆 | 独立开关与字段名 |
## Acceptance Criteria
- [x] `include_wyckoff=1` 返回约定 `wyckoff` 键;默认不强制计算
- [x] 合成 fixture:能检出 TR + 至少一类事件;VP POC 可测
- [x] 主站可开关绘制区间/阶段/事件/VP
- [x] golden 缠论基线不变
- [x] TEST/IMPL/CHANGELOG/TRACEABILITY + CODE_REVIEW
## Rollback
`git revert`;关闭 UI 开关即可无图面影响。
## Risk Review
- `docs/RISK_REVIEW/ECR-003.md` — N/A(展示分析,非 Live 策略)
## Linked
- IDEA: `docs/IDEA/IDEA-004-wyckoff-main.md`
- PRODUCT_SPEC / ENGINEERING_SPEC: 同目录 ECR-003-*
- EXPERIMENT: N/A
- TRACEABILITY: Yes
- CODE_REVIEW: `docs/CODE_REVIEW/ECR-003.md` — Approve
-69
View File
@@ -1,69 +0,0 @@
# ECR-004
**Title:** 威科夫区间评分硬化与主站 VP 绘图减负
**Status:** Done (Reviewed)
**Date:** 2026-08-06
**Change Level:** L2
## Change
跟进 ECR-003 CODE_REVIEW Findings:改进交易区间选取启发式、阶段最小长度、收紧单测;主站 VP/叠层降低 Lightweight series 数量;`include_wyckoff` 与主周期分析同门闩。
## Motivation
003 已 Approve 合入;质量与内存项不得回塞已审变更,须独立可审闭环。
## Scope
### Allowed
- `chanlun/analysis/wyckoff/range.py` / `events.py`(阶段)启发式与单测
- `web/static/js/app/chart_tv.js` 威科夫 VP/填充绘制路径
- `web/api/analyze.py``elements_only` 时不跑威科夫;默认 `vp_bins` 上限 24
- ESS 文档与契约测试补充断言(不删既有 `wyckoff` 键)
### Forbidden
- 改笔/段/中枢/买卖点语义
- `config/` / `strategies/`
- `/chan_tv`
- 新数据源 / 订单流
- **按币种独立参数表**(全局 ATR 相对即可;当前以 BTC 场景验证)
## DecisionsApprove 时锁定)
- VP**A+C**(前端 Top-N 有量 bin + 服务端 bins 上限 24
- 不做 per-symbol 参数
## Risk
| Risk | Mitigation |
|------|------------|
| TR 结果相对 003 漂移 | 合成夹具锁定高低与起点;文档标明启发式迭代 |
| 前端 VP 观感变化 | 保留 POC/VAH/VAL;密度用 Top-N |
| 回归 | 扩展 `tests/test_wyckoff.py` + 既有契约套件 |
## Acceptance Criteria
- [x] 合成箱体夹具:`trading_range` 高低接近箱体边界,起点不落入明显前置趋势段
- [x] 开启 VP 时主图新增 series 数显著低于「每 bin 一条」(目标:填充+VP ≤ ~15 或等价合并策略)
- [x] 阶段输出满足最小跨度或合并退化段;文档说明规则
- [x] `elements_only=true` 即使 `include_wyckoff=1` 也不返回 `wyckoff`
- [x] golden 缠论基线不变;相关 pytest 绿
- [x] TEST/IMPL/CHANGELOG/TRACEABILITY + CODE_REVIEW
## Rollback
`git revert`UI 关威科夫即可无图面影响。
## Risk Review
- `docs/RISK_REVIEW/ECR-004.md` — N/A(展示/启发式,非 Live 策略)
## Linked
- IDEA: `docs/IDEA/IDEA-005-wyckoff-harden.md`
- 上游: `docs/CODE_REVIEW/ECR-003.md` Findings 15
- PRODUCT_SPEC / ENGINEERING_SPEC: 同目录 ECR-004-*
- TRACEABILITY: Yes
- CODE_REVIEW: `docs/CODE_REVIEW/ECR-004.md` — Approve
@@ -1,60 +0,0 @@
# ECR-007
**Title:** Wyckoff Live Structure
**Status:** Approved
**Date:** 2026-08-07
**Change Level:** L2
**Human:** Approved (LOOP-RUN-005 Start Authorization)
## Change
Add **Live / Developing** structure layer beside **Confirmed** Wyckoff engine: lifecycle, FORMING candidates (Spring/SOS/LPS/UTAD), explainable confidence, Summary partition. Keep Confirmed thresholds unchanged; execution may only consume Confirmed.
## Motivation
LOOP-RUN-005 — domain-state complexity under Adapter v0.1 STABLE (Confirmed ≠ Live ≠ execution).
## Scope
### Allowed (IN)
- `chanlun/analysis/wyckoff/live.py` + engine assembly
- lifecycle / confirmed / live payload
- Event candidates + confidence
- API contract + Summary UI
- tests + docs notes (WYCKOFF-LIVE-STRUCTURE-001)
### Forbidden (OUT)
- execution signal automation / auto trading
- strategy / maker / decide_quotes / `strategies/**`
- lowering Confirmed thresholds
- Live candidate replacing Confirmed
- ESS / Loop / Adapter changes
## Risk
| Risk | Mitigation |
|------|------------|
| Live → execution | `execution_signal_from_wyckoff` source=confirmed only; live-only → None |
| Confirmed pollution | candidates never written to confirmed.events |
| Domain confusion in UI | Summary Confirmed vs Live partitions |
## Acceptance Criteria
- [ ] Approved BD-2026-007
- [ ] Confirmed logic not relaxed
- [ ] Live ≠ execution signal (tests)
- [ ] Lifecycle verifiable
- [ ] Artifact chain + Gate PASS
## Rollback
- Disable live assembly; remove live.py; revert Summary partition
## Linked
- Note: `docs/notes/WYCKOFF-LIVE-STRUCTURE-001.md` (FROZEN)
- BACKEND_DESIGN: `docs/BACKEND_DESIGN/BD-2026-007-wyckoff-live-structure.md`
- ENGINEERING_SPEC: `docs/ENGINEERING_SPEC/ECR-007-wyckoff-live-structure.md`
- Loop: LOOP-RUN-005
@@ -1,105 +0,0 @@
# Engineering Spec: ECR-001 引擎 + Web 重构
## Related
| Doc | Link |
|-----|------|
| ECR | `docs/ECR/ECR-001-chan-web-restructure.md` |
| PRODUCT_SPEC | `docs/PRODUCT_SPEC/ECR-001-restructure.md` |
| ADR | `docs/ADR/ADR-001-package-layout.md` |
## Module Design
### `chanlun/core/*`
| 项 | 值 |
|----|-----|
| Responsibility | K 线单元、合并 K、笔/段/中枢/买卖点数据结构与枚举 |
| Can | 表达缠论基础对象 |
| Cannot | 拉行情、写 Flask 路由 |
| Layer | domain |
### `chanlun/pipeline/*`
| 项 | 值 |
|----|-----|
| Responsibility | `ChanLun` 编排、`TF_DF` 门面、builders |
| Can | 从 DataFrame 构建结构 |
| Cannot | 改公开方法语义 |
| Layer | application |
### `chanlun/indicators/*` / `chanlun/analysis/*`
| 项 | 值 |
|----|-----|
| Responsibility | MACD 状态、Zone/Classifier/Trend 等分析 |
| Layer | domain / application |
### Root shims (`ChanLun.py`, `ChanEnum.py`, …)
| 项 | 值 |
|----|-----|
| Responsibility | 转发到 `chanlun.*`,兼容 strategies |
| Cannot | 含业务逻辑 |
### `web/config.py` + `web/services/*` + `web/api/*`
| 项 | 值 |
|----|-----|
| Responsibility | 配置、行情、分析、序列化、HTTP |
| Cannot | 修改缠论算法 |
### `web/static/js/app/*`
| 项 | 值 |
|----|-----|
| Responsibility | 图表、overlay、UI、API 客户端 |
| Cannot | 本轮引入 React 构建 |
## Interfaces
```python
class TF_DF:
# 公开方法签名保持与重构前一致
def get_kl_data(self, dataframe): ...
def get_bi_list(self, dataframe): ...
# ... 门面委托 builders
class ChanLun:
def init_dataframes(...): ...
```
`/api/analyze`:查询参数与 JSON 顶层字段兼容。
## Algorithm Sketch
1. 物理迁移模块到 `chanlun/`,修正 import
2. 根 shim 再导出
3. `TF_DF` 方法体迁移到 builders,门面调用
4. Web 按职责拆文件,路由注册不变
5. HTML 抽 JS;未接线 TS 入 `_unused`
## Error Handling
| Case | Behavior |
|------|----------|
| 行情失败 | 保持现有 JSON error |
| 分析异常 | 保持现有日志与错误返回 |
## Config / Constants
| Name | Source |
|------|--------|
| DATA_SERVICE_URL | env / config |
| HTTP(S)_PROXY | env / config(替代硬编码 7897 |
| MACD periods | config |
## Test Plan Pointer
- `tests/test_golden_pipeline.py` + `tests/fixtures/golden_*`
- `web/tests/` analyze 契约冒烟
- `python -c "from ChanLun import ChanLun"`
## Migration / API Impact
无破坏性 API 变更;仅内部路径变化。
@@ -1,23 +0,0 @@
# ENGINEERING_SPEC — ECR-002
**Status:** Implemented
**Date:** 2026-08-06
## Design
1. **包目录** `web/services/runtime/`(不用平铺 `runtime_*.py`
2. **边界**
- `state`:可变全局与客户端
- `timeframes`:周期工具
- `market_data`:行情
- `indicators`:技术指标列
- `analyze`:缠论编排 + 趋势分类
- `serialize`JSON 整形
- `__init__`:门面 + 历史 `import *` 兼容再导出
3. **测试**facade / analyze_chan 键 / serialize / HTTP mock 契约 / TF_DF init / golden
4. **chart_tv 拆分**:本轮不做(仍可选后续 ECR
## Open questions(已决)
- [x] 采用包目录 `services/runtime/`
- [x] chart_tv 拆分不纳入本 PR
@@ -1,46 +0,0 @@
# ENGINEERING_SPEC — ECR-003
**Status:** Approved
**Date:** 2026-08-06
## Package
`chanlun/analysis/wyckoff/`
- `engine.py``analyze_wyckoff(df) -> dict`
- `range.py` — 交易区间检测(ATR 容差震荡箱)
- `phases.py` — AE 状态机
- `events.py` — Spring/SOS/LPS/UTAD(及 distribution 对称)
- `volume_profile.py` — 区间内分桶 VP
- `__init__.py` — 导出 `analyze_wyckoff`
## API
`GET /api/analyze?include_wyckoff=1``result["wyckoff"]`
```json
{
"trading_range": {"start_time","end_time","high","low","mid","active"},
"bias": "accumulation|distribution|unknown",
"phases": [{"phase","label","start_time","end_time"}],
"events": [{"type","time","price","note","volume_ratio","volume_ok"}],
"volume_profile": {"bins":[{"price","volume"}],"poc","vah","val","bin_count"},
"volume_confirm": {"avg_volume","event_checks":{}}
}
```
默认 `include_wyckoff` 假:可不返回或返回 `null`(实现选:不返回键以减负)。
## Detection heuristics
1. ATR(14) 容差;扫描最近窗口找高低点接近的连续段作为 TR。
2. 阶段:价格在 TR 内相对位置 + 假破/真破时间序。
3. Spring:下破 TR.low 后收回且收盘回到区间内;量能相对均量判断。
4. SOS:收盘站上 TR.high 且放量。
5. LPSSOS 后回踩不破 mid/high 带且缩量。
6. UTAD:上破后跌回区间内(派发)。
7. VPtypical=(H+L+C)/3volume 加权分桶,VA≈70% 围绕 POC。
## Frontend
主站 checkbox + `chart_view` 传参;`chart_tv.js` 绘制。
@@ -1,43 +0,0 @@
# ENGINEERING_SPEC — ECR-004
**Status:** Approved(实现锁定:评分选段;VP=A+C;无币种参数)
**Date:** 2026-08-06
## Range scoring
替换「仅取最长合格窗口」:
1. 仍在 `lookback` + `tail_reserve` 框架内扫描候选段(步长 -4)。
2. 硬门槛不变:near_hi/lo≥2、inside≥0.75、宽度上限等。
3. 分数:`touch_density*50 + inside*30 - (width/ATR)*3 + min(length/40, 2)`,取最高。
4. 单测:`low∈[38,42]``high∈[58,62]`,起点不早于箱体(容差 8 根);`abs_start_idx >= 12`
## Phases
- 非重叠链式切分;每段至少 `min_bars=3`
- 尾部空间不足则延长上一段并停止新增(避免 D/E 完全重合双画)。
## API gate
```text
if include_wyckoff and not elements_only:
result["wyckoff"] = analyze_wyckoff(..., vp_bins∈[10,24])
```
默认 `wyckoff_vp_bins=24`,上限 24。
## Frontend VPA+C
- 填充线 6→3
- 有量 bin 按 volume Top-8 绘制 + POC/VAH/VAL
- 目标:区间填充+边框+VP ≈ ≤15 series 量级
## Tests
- `tests/test_wyckoff.py` 收紧
- `elements_only=true&include_wyckoff=1``wyckoff`
- 不改 golden 缠论 JSON
## Non-goals
- 按币种独立参数(全局 ATR 相对;以 BTC 场景验证)
@@ -1,26 +0,0 @@
# ENGINEERING_SPEC — ECR-007 Wyckoff Live Structure
**ECR:** ECR-007
**BD:** BD-2026-007
**Status:** Approved
## Intent
Operators observe FORMING Wyckoff structure without feeding Live into execution.
## Modules
| Module | Role |
|--------|------|
| `events.py` / `range.py` | Confirmed facts |
| `live.py` | Live candidates + confidence + lifecycle hint |
| `engine.py` | Assemble cycles[].confirmed / .live |
| `execution_signal_from_wyckoff` | Confirmed-only gate |
## Lifecycle
`UNKNOWN → FORMING → CONFIRMED → COMPLETED`
## Non-goals
strategies, maker, Live-as-signal, Confirmed threshold cuts.
@@ -1,38 +0,0 @@
# Handoff
**From:** ARCHITECT
**To:** ENGINEER
**ECR:** ECR-001
**State:** design → coding
**Date:** 2026-08-05
## Artifacts
- [x] ECR (Approved)
- [x] PRODUCT_SPEC
- [x] ENGINEERING_SPEC
- [x] RISK_REVIEW (N/A)
- [x] ADR-001
- [x] TRACEABILITY
- [ ] TEST_REPORT / CODE_REVIEW / EXP (engineer / reviewer)
## Restrictions — Do not modify
- `config/``strategies/` 内容
- 缠论算法语义、`/api/analyze` 破坏性变更
- 计划文件本身
## Goal for receiver
按 ENGINEERING_SPEC 完成包化、TF_DF 拆分、Web 分层、前端模块化、golden 测试与文档收尾。
## Done for this hop
- [x] ESS docs 落盘
- [x] ECR Approved(计划实施授权)
## References
- `docs/ECR/ECR-001-chan-web-restructure.md`
- `docs/ENGINEERING_SPEC/ECR-001-restructure.md`
- Plan: ESS Chan Refactor1B+2B
@@ -1,29 +0,0 @@
# Handoff
**From:** ENGINEER
**To:** REVIEWER
**ECR:** ECR-001
**State:** coding → review
**Date:** 2026-08-05
## Artifacts
- [x] ECR (Approved / Done pending review)
- [x] PRODUCT_SPEC / ENGINEERING_SPEC / ADR / RISK N/A
- [x] IMPLEMENTATION_REPORT
- [x] TEST_REPORT
- [x] CODE_REVIEW → Approve`docs/CODE_REVIEW/ECR-001.md`
## Goal for receiver
对照 ECR-001 Acceptance 做代码审阅;确认 strategies/config 无 diffgolden 与路由契约通过。
## Reviewer outcome
**Approve**strategies 仅 import 迁移,记为范围修订)。Next → release_manager。
## References
- `docs/IMPLEMENTATION_REPORT/ECR-001.md`
- `docs/TEST_REPORT/ECR-001.md`
- `docs/CHANGELOG/CHANGELOG.md`
@@ -1,30 +0,0 @@
# HANDOFF — ECR-002 engineer → reviewer
**From:** ENGINEER
**To:** REVIEWER
**Date:** 2026-08-06
**ECR:** ECR-002
## Ask
对照 ECR-002 Acceptance 做代码审阅;确认 strategies/config 无 diffgolden + 新契约测试通过。
## Artifacts
- `docs/ECR/ECR-002-runtime-split.md`
- `docs/IMPLEMENTATION_REPORT/ECR-002.md`
- `docs/TEST_REPORT/ECR-002.md`
- `docs/ENGINEERING_SPEC/ECR-002-runtime-split.md`
## Diff focus
- `web/services/runtime/`(新包)
- 删除原 `web/services/runtime.py`
- `web/tests/test_*.py``tests/test_tf_df_init.py`
- ESS docs 更新
## Out of scope this round
- `chart_tv.js` 拆分
- 主站 WebSocket
- strategies/config

Some files were not shown because too many files have changed in this diff Show More