Compare commits
60
Commits
v1.0.0
..
be442783a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be442783a1 | ||
|
|
f11c6507b0 | ||
|
|
bfdb2f2e2a | ||
|
|
06928d1d5f | ||
|
|
431e776905 | ||
|
|
360e4e1c41 | ||
|
|
f716ddd149 | ||
|
|
c57adc8cb0 | ||
|
|
97457b0518 | ||
|
|
0745e73b4f | ||
|
|
545fcc7c77 | ||
|
|
34a8f37b39 | ||
|
|
0a0fd2f682 | ||
|
|
c8a0062707 | ||
|
|
f5f456c523 | ||
|
|
d4fbe9d905 | ||
|
|
ef584b8d49 | ||
|
|
2b9a837a58 | ||
|
|
ef2a85dd8c | ||
|
|
27a4360ce8 | ||
|
|
7c13781f90 | ||
|
|
d13a0f85bc | ||
|
|
550eafdd9d | ||
|
|
99ae072cc6 | ||
|
|
d37bfcb26b | ||
|
|
2a59dcb9dc | ||
|
|
4a4f8727fd | ||
|
|
6c843639d5 | ||
|
|
9f4d7fec73 | ||
|
|
d2fcb27d01 | ||
|
|
0b4d7693b8 | ||
|
|
3b14bba247 | ||
|
|
75fbf4167b | ||
|
|
54792fe015 | ||
|
|
95f2b614a2 | ||
|
|
1661bbbac9 | ||
|
|
631d97e493 | ||
|
|
01c4a4ab4d | ||
|
|
28075173af | ||
|
|
181bca303f | ||
|
|
6259f7380b | ||
|
|
32c18f0201 | ||
|
|
9b72173285 | ||
|
|
7e339d2a54 | ||
|
|
66061f79a1 | ||
|
|
b286cb0137 | ||
|
|
a243edd2d3 | ||
|
|
206b27fe72 | ||
|
|
7f393b93ed | ||
|
|
5c10e35b76 | ||
|
|
8c165f11cd | ||
|
|
97e77847d0 | ||
|
|
90499533fb | ||
|
|
8ee11317d3 | ||
|
|
1e60ab3bfa | ||
|
|
d3188ca83c | ||
|
|
ac6be80278 | ||
|
|
081a57a90e | ||
|
|
df27b4dde8 | ||
|
|
9f1e7361b6 |
+37
-28
@@ -1,42 +1,51 @@
|
||||
# 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
|
||||
|
||||
# Office documents kept alongside the repo but not part of it.
|
||||
# "~$" files are Excel's lock files, recreated every time a workbook is opened.
|
||||
*.xlsx
|
||||
*.xls
|
||||
~$*
|
||||
|
||||
# Local data
|
||||
data/
|
||||
|
||||
# Exchange history fetched by research/live/*.py. 40MB and re-fetchable from
|
||||
# the venue, so it stays local; the small result CSVs it feeds are committed.
|
||||
research/live/cache/
|
||||
|
||||
# Scratch outputs from short shakedown runs, superseded by the real collection.
|
||||
research/out/archive/
|
||||
|
||||
# Per-trade simulation dumps from research/step*.py. 70MB+ and regenerable by
|
||||
# rerunning the step; the summaries they feed live in HANDOFF.md.
|
||||
research/out/*.feather
|
||||
|
||||
# Virtualenvs. venv writes its own .gitignore since 3.11, but only for the
|
||||
# directory it creates — declare it here so other layouts are covered too.
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Local tooling
|
||||
.gstack/
|
||||
research/out/*.jsonl.gz
|
||||
research/out/penetration.csv
|
||||
research/out/shadow_*.csv
|
||||
research/out/run_meta_*.json
|
||||
|
||||
# Telegram 凭据。**不要提交**
|
||||
research/live/deploy/tg.env
|
||||
|
||||
@@ -1,114 +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
|
||||
|
||||
- ESS 文档:`docs/PROJECT_PROFILE.md`、`docs/ECR/`、`docs/ENGINEERING_SPEC/`
|
||||
- **正式引擎包**:`chanlun/`;strategies / web 已用 `from chanlun import ...`
|
||||
- 根目录 `Chan*.py` / `TF_DF.py` 仍为 **兼容 shim**(旧脚本可用)
|
||||
|
||||
## 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
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanBI import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanBIZS import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanBSP import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanCTime import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanEnum import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanHeng import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanKLC import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanKLU import * # noqa: F403
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanLun_Classifier import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.indicators.ChanMACD import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.indicators.ChanMACDHistSet import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.indicators.ChanMACDSeg import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.indicators.ChanMACDUnitTF import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanPY import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanPivotClassifier import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanPivotMonitor import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanSBI import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanSEG import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.ChanZS import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.ChanZone import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.core.Chan_FX_Box import * # noqa: F403
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.analysis.Find_Trend import * # noqa: F403
|
||||
@@ -0,0 +1,130 @@
|
||||
# Chan — 缠论分析引擎
|
||||
|
||||
把 OHLCV K 线拆成缠论结构(K 线单元 → 合并 K 线 → 分型 → 笔 → 线段 → 中枢 → 买卖点),
|
||||
配一个 TradingView Charting Library 的 Web 界面,外加一套验证信号有效性的回测脚本。
|
||||
|
||||
标的不限:加密永续(ccxt)与 A 股(akshare)都走同一条分析链路。
|
||||
|
||||
```
|
||||
chanlun/ 缠论引擎,纯 pandas/numpy,无外部指标库依赖
|
||||
web/ Flask API + 图表界面
|
||||
research/ 信号有效性验证脚本(step1 ~ step30)
|
||||
data/ 本地 K 线(freqtrade 的 feather 格式)
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
需要 Python ≥ 3.11(pandas 3.x / numpy 2.x 的要求,不是本项目代码的限制)。
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt # 核心运行时,8 个包
|
||||
.venv/bin/pip install -r requirements-dev.txt # 另加测试与 research/ 所需
|
||||
```
|
||||
|
||||
## 跑 Web
|
||||
|
||||
```bash
|
||||
cd web
|
||||
../.venv/bin/python app.py # 默认 http://0.0.0.0:8128
|
||||
```
|
||||
|
||||
从仓库根跑 `.venv/bin/python web/app.py` 也可以——Python 会把脚本所在目录放进 `sys.path`。
|
||||
但**不能用 `python -m web.app`**,也不能 `import web.app`:`web/` 内部是无前缀导入
|
||||
(`import config`、`from api.analyze import bp`),`-m` 方式下 `sys.path` 里是仓库根而不是
|
||||
`web/`,会 `ModuleNotFoundError: No module named 'config'`。
|
||||
|
||||
配置全部走环境变量,见 `web/config.py`:
|
||||
|
||||
| 变量 | 默认值 | 用途 |
|
||||
|------|--------|------|
|
||||
| `FLASK_HOST` / `FLASK_PORT` | `0.0.0.0` / `8128` | 监听地址 |
|
||||
| `DATA_SERVICE_URL` | `https://provider.jackyu66.com` | 行情 REST 源 |
|
||||
| `DATA_SERVICE_WS_URL` | `wss://jackyu66.com/ws` | 行情 WebSocket 源 |
|
||||
| `ASHARE_DP_URL` | `http://103.179.242.166:8000` | A 股数据源 |
|
||||
| `CHAN_HTTP_PROXY` | 未设置则不走代理 | ccxt / HTTP 代理 |
|
||||
| `MACD_FACTOR` / `MACD_SMOOTH` | `1` / `1` | MACD 周期倍数,默认 12/26/9 |
|
||||
|
||||
主要接口:`GET /api/analyze` 返回某标的某周期的完整缠论结构,`/api/klines/recent`
|
||||
取最新 K 线,`/api/trend_filter` 与 `/api/trend_detail` 做多周期趋势筛选,
|
||||
`/api/symbols`、`/api/search_stock`、`/api/sectors` 等负责标的检索。页面在 `/` 与 `/chan_tv`。
|
||||
|
||||
## 作为库使用
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from chanlun import TF_DF
|
||||
|
||||
# 需要 date/open/high/low/close/volume 六列,date 为 datetime
|
||||
df = pd.read_feather("data/binance/futures/BTC_USDT_USDT-1h-futures.feather")
|
||||
|
||||
tf = TF_DF(df, interval=1, timeframe="1h")
|
||||
|
||||
len(tf.klu_list) # K 线单元
|
||||
len(tf.klc_list) # 合并 K 线(处理包含关系后)
|
||||
len(tf.bi_list) # 笔
|
||||
len(tf.seg_list) # 线段
|
||||
len(tf.zs_list) # 中枢
|
||||
tf.bi_list[-1].dir # Chan_BI_DIR.DOWN
|
||||
tf.chanmacd # MACD 结构分析(背驰判定用)
|
||||
```
|
||||
|
||||
**`interval` 的单位是分钟**,对传入的 df 做重采样;`interval=1` 是特例,表示原样使用、
|
||||
不重采样。所以拿 1h 的 feather 要传 `interval=1`,拿 1m 数据想看 1h 才传 `interval=60`:
|
||||
|
||||
```python
|
||||
df1m = pd.read_feather("data/binance/futures/BTC_USDT_USDT-1m-futures.feather")
|
||||
TF_DF(df1m, interval=5, timeframe="5m")
|
||||
TF_DF(df1m, interval=60, timeframe="1h")
|
||||
```
|
||||
|
||||
传错不会报错,只会静默给出错误周期的结构——1h 数据配 `interval=4` 相当于按 4 分钟
|
||||
重采样,结果与 `interval=1` 完全相同。
|
||||
|
||||
## 分析流程
|
||||
|
||||
`TF_DF.init_TF_DF()` 按顺序做这几步,每步的实现在 `chanlun/pipeline/builders/` 下同名文件:
|
||||
|
||||
1. `resample_to_interval` — 重采样(`interval != 1` 时)
|
||||
2. `add_indicators` — 追加 33 列指标(MACD / BBANDS / EMA / RSI / ATR 等)
|
||||
3. `cal_kl_data` → `klu_list` — K 线单元
|
||||
4. `get_klc_list` → `klc_list` — 按包含关系合并 K 线,并标记分型
|
||||
5. `cal_bi_list` → `bi_list` — 笔
|
||||
6. `cal_bi_zs_list_pure` → `bi_zs_list` — 笔中枢
|
||||
7. `get_seg_list` → `seg_list` — 线段
|
||||
8. `get_zs_list` / `get_big_zs_list` — 中枢与大级别中枢
|
||||
9. `ChanMACD(klu_list)` — MACD 段 / 柱堆结构,供背驰判定
|
||||
|
||||
## 数据
|
||||
|
||||
`data/<交易所>/futures/<SYMBOL>-<周期>-futures.feather`,即 freqtrade 的下载格式,
|
||||
如 `data/binance/futures/BTC_USDT_USDT-1h-futures.feather`。
|
||||
`research/lib/data.py` 负责定位:`BTC/USDT:USDT` + `1h` 会解析到上面这个路径,
|
||||
找不到本地文件则回落到远端拉取。
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest chanlun/tests web/tests -q
|
||||
```
|
||||
|
||||
`chanlun/tests/test_ta_compat.py` 有个**需要注意的陷阱**:它把 `chanlun/indicators/ta.py`
|
||||
的输出逐 bar 钉在 TA-Lib 上,但 **TA-Lib 不存在时会静默跳过**。也就是说改了 `ta.py`
|
||||
之后在没装 TA-Lib 的环境里跑,测试会显示通过,其实一项都没验证。改动那个文件时请先装:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libta-lib0 ta-lib-dev
|
||||
.venv/bin/pip install TA-Lib technical
|
||||
```
|
||||
|
||||
## 已知问题
|
||||
|
||||
- **`web/DEPLOY_GUIDE.md` 已失效**:它引用的 `deploy_venv.sh`、`stop_venv.sh`、
|
||||
`status_venv.sh` 等 6 个脚本都在 `7f393b9` 精简提交里删掉了,目前没有部署脚本。
|
||||
两个 systemd unit 文件(`web/chanlun-web*.service`)仍可参考,但它们用 gunicorn
|
||||
且写死端口 8123,与 `config.py` 默认的 8128 不一致,gunicorn 也不在依赖清单里。
|
||||
- **`web/README.txt` 已过时**:它说的 `web/requirements.txt` 不存在,依赖清单在仓库根目录。
|
||||
- **四个零引用的死文件**:`chanlun/analysis/` 下的 `ChanPY.py`、`ChanLun_Classifier.py`、
|
||||
`Find_Trend.py`、`ChanHeng.py` 全项目无人引用。`ChanPY.py` 依赖未安装的外部 chan.py 库,
|
||||
另外三个需要 matplotlib / mplfinance / xgboost / scikit-learn——这些都**不在**依赖清单里,
|
||||
是有意为之。要用得自行安装。
|
||||
@@ -1,2 +0,0 @@
|
||||
"""兼容 shim — 请优先 from chanlun import ..."""
|
||||
from chanlun.pipeline.timeframe import TF_DF # noqa: F401
|
||||
@@ -14,12 +14,10 @@ from chanlun.core.ChanSBI import ChanSBI
|
||||
from chanlun.core.ChanSEG import ChanSEG
|
||||
from chanlun.core.ChanZS import ChanZS
|
||||
from chanlun.core.ChanBSP import ChanBSP
|
||||
import talib.abstract as ta
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.dates import DateFormatter, date2num
|
||||
import matplotlib.patches as patches
|
||||
from technical.util import resample_to_interval
|
||||
from decimal import Decimal
|
||||
from chanlun.pipeline.orchestrator import ChanLun
|
||||
import xgboost as xgb
|
||||
|
||||
@@ -2,7 +2,7 @@ import ccxt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import mplfinance as mpf
|
||||
from talib import MACD, SMA
|
||||
from chanlun.indicators import ta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import datetime as dt
|
||||
@@ -249,8 +249,9 @@ def analyze_higher_timeframe(df_30m):
|
||||
# 8. Back-divergence detection (enhanced)
|
||||
def detect_back_divergence(df, strokes, higher_trend):
|
||||
try:
|
||||
macd, signal, hist = MACD(df['Close'], fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
sma20 = SMA(df['Close'], timeperiod=20)
|
||||
macd_df = ta.MACD(df['Close'], fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
macd, hist = macd_df['macd'], macd_df['macdhist']
|
||||
sma20 = ta.SMA(df['Close'], timeperiod=20)
|
||||
df['macd'] = macd
|
||||
df['hist'] = hist
|
||||
df['sma20'] = sma20
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
"""快速三类买卖点(引擎内称第四类,B4/S4)。
|
||||
|
||||
原本长在 `research/lib/fast_bsp3.py`,现移入引擎作为唯一实现,研究脚本改为转发导入,
|
||||
这样回测口径与 web 图表永远一致。
|
||||
|
||||
命名说明:缠论原文里没有「第四类买卖点」,但 B4/S4 也不只是「B3/S3 提前几根」——
|
||||
两者的选样口径不同,统计性质符号相反,故单独立类。形态条件是实时可判的:
|
||||
|
||||
中枢已成 -> 收盘突破 zg -> 收盘未跌回中枢 -> 重新上行
|
||||
|
||||
最后一步发生的当根就能下单,滞后约 2 根,而引擎 B3/S3 要等 pullback_bi.sure_time,
|
||||
滞后 9~10 根。但差别不止滞后:
|
||||
|
||||
判据 引擎用笔端点事后判(回拉笔低点 >= zg),本函数用收盘价实时判
|
||||
方向 引擎由离开笔方向决定,本函数由收盘从哪一侧突破决定
|
||||
口径 627 个中枢里引擎发 625 个信号(几乎不筛),本函数只认 212 个(34%);
|
||||
被拒的多数是「中枢确认时价格早已离开、此后再没回来」的历史区间
|
||||
|
||||
step30 同条件对拍(同一套 pure 笔中枢、同一组过滤器、同样的 1.5/3.0/48 出场):
|
||||
|
||||
原始 PF +大级别同向 +同向+阶梯 胜率 t值
|
||||
引擎 B3/S3 0.66 0.66 0.71 27.4% -18.76
|
||||
本函数 B4/S4 1.59 1.85 2.26 47.1% +10.09
|
||||
|
||||
同一组过滤器对 B4 有效、对 B3 无效,滞后差解释不了这一点(入场后移 1~4 根只是
|
||||
PF 3.18->2.53 的平滑衰减)。且 SL1.5/TP3.0 下随机入场胜率约 33%,引擎那 27.4%
|
||||
低于随机——它选中的是一批系统性反向的样本,不是「晚了所以差」。
|
||||
|
||||
require_touch 控制是否强求回抽碰到中枢边界。step32/33 的实测表明强求反而更差:
|
||||
这等于排除掉「突破后一去不回头」的强势段,而那正是缠论里最强的趋势形态。
|
||||
故默认 False(笔数 +21%、滞后 -1 根、PF 2.26→2.37)。
|
||||
|
||||
判据本身(收盘越过前一根极值)没有独立预测力:裸用 15 万笔样本 PF 0.95,
|
||||
把中枢换成「近20根高点」这类伪阻力位后 PF 0.90。alpha 全部来自中枢结构,
|
||||
判据只负责在这个已知价位上确认动能恢复。它也不能用于识别笔端点——
|
||||
入场前的回抽极值命中笔端点±2根的比例 19.3%,低于随机基准 22%。
|
||||
|
||||
全部判定只使用当根及之前的数据,无未来函数。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from chanlun.core.ChanEnum import Chan_FX_TYPE
|
||||
|
||||
# 中枢的「可用时刻」取第几笔的确认时间。
|
||||
# -1 = 最后一笔(历史默认)。中枢每吸收一根K线,最后一笔就可能后移,
|
||||
# 于是 available_ts 跟着漂——这是右边缘重画的根因(见 HANDOFF §5.41)。
|
||||
# 2 = 第三笔。三笔重叠即中枢成立,此后不再变,理论上更早也更稳。
|
||||
# ⛔ step47 已判定 bis[2] 作废(毛 R 0.933 → −0.000,见 HANDOFF §5.42)。
|
||||
# 开关保留只为可复现那次 A/B,**不要改默认值**。
|
||||
import os as _os
|
||||
|
||||
AVAIL_BI_INDEX = -1
|
||||
|
||||
|
||||
def _resolve_avail_bi(avail_bi: int | None) -> int:
|
||||
"""优先级:显式入参 > 环境变量 CHAN_AVAIL_BI > 模块默认。
|
||||
|
||||
环境变量在调用时读取而非 import 时——ProcessPoolExecutor 在 fork 启动方式下
|
||||
子进程会继承已 import 的模块,import 时读就固化成父进程的值了。
|
||||
"""
|
||||
if avail_bi is not None:
|
||||
return avail_bi
|
||||
return int(_os.environ.get("CHAN_AVAIL_BI", AVAIL_BI_INDEX))
|
||||
|
||||
|
||||
def timestamps_ms(src: pd.DataFrame) -> np.ndarray:
|
||||
"""取毫秒时间戳。研究侧的 df 自带 timestamp,web 侧的不一定,故按 date 回退。
|
||||
|
||||
回退写法不能用 `date.astype("int64") // 10**6`:该值单位取决于列精度,
|
||||
对毫秒精度的列会把时间戳砸平。
|
||||
"""
|
||||
if "timestamp" in src.columns:
|
||||
return src["timestamp"].to_numpy()
|
||||
d = pd.to_datetime(src["date"])
|
||||
if getattr(d.dt, "tz", None) is None:
|
||||
d = d.dt.tz_localize("UTC")
|
||||
return (d.dt.tz_convert("UTC").dt.tz_localize(None)
|
||||
.astype("datetime64[ms]").astype("int64").to_numpy())
|
||||
|
||||
|
||||
def ensure_timestamp(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""保证 df 带 timestamp 列,缺失时补一份副本,不改动调用方的对象。"""
|
||||
if "timestamp" in df.columns:
|
||||
return df
|
||||
out = df.copy()
|
||||
out["timestamp"] = timestamps_ms(out)
|
||||
return out
|
||||
|
||||
|
||||
def build_htf_zones(df_htf: pd.DataFrame, tf: str, chan=None, avail_bi: int | None = None) -> pd.DataFrame:
|
||||
"""算 pure 笔中枢,返回带生效时间的区间表。
|
||||
|
||||
available_ts —— 该中枢最早可被使用的时间戳(其确认时刻)。
|
||||
传入已构建好的 chan 可避免重复跑一遍 pipeline(大数据集上省一半时间)。
|
||||
"""
|
||||
if chan is None:
|
||||
from chanlun import TF_DF
|
||||
|
||||
chan = TF_DF(df_htf, 1, tf)
|
||||
zs_list = chan.cal_bi_zs_list_pure(chan.bi_list)
|
||||
# 用引擎自己的 dataframe 对齐,避免调用方传入的 df 与引擎内部行数不一致
|
||||
src = chan.dataframe if getattr(chan, "dataframe", None) is not None else df_htf
|
||||
return zones_from_zs_list(zs_list, src, avail_bi=avail_bi)
|
||||
|
||||
|
||||
def zones_from_zs_list(zs_list, src: pd.DataFrame, avail_bi: int | None = None) -> pd.DataFrame:
|
||||
"""把已算好的 pure 笔中枢转成区间表。
|
||||
|
||||
调用方手工跑过 cal_bi_zs_list_pure 时走这里,免得再算一遍(web 的 analyze_chan
|
||||
就是这种用法)。
|
||||
"""
|
||||
ts_of = dict(zip(src["date"].dt.strftime("%Y-%m-%d %H:%M:%S"), timestamps_ms(src)))
|
||||
# 中枢可用时刻取第几笔。在循环外解析一次,别让每个中枢都去读一遍环境变量。
|
||||
i = _resolve_avail_bi(avail_bi)
|
||||
|
||||
rows = []
|
||||
for zs in zs_list:
|
||||
bis = getattr(zs, "bi_list", [])
|
||||
if not bis:
|
||||
continue
|
||||
# 笔数不够时退回最后一笔(i=2 需要至少 3 笔才成立)
|
||||
key_bi = bis[i] if (i == -1 or len(bis) > i) else bis[-1]
|
||||
sure_key = str(getattr(key_bi, "sure_time", "") or "")
|
||||
end_key = str(getattr(key_bi, "end_time", "") or "")
|
||||
avail = ts_of.get(sure_key) or ts_of.get(end_key)
|
||||
if avail is None:
|
||||
continue
|
||||
start_key = str(bis[0].start_time)
|
||||
rows.append({
|
||||
"zg": float(zs.zg), "zd": float(zs.zd),
|
||||
"gg": float(getattr(zs, "gg", zs.zg)), "dd": float(getattr(zs, "dd", zs.zd)),
|
||||
"start_ts": ts_of.get(start_key, avail),
|
||||
"available_ts": int(avail),
|
||||
})
|
||||
out = pd.DataFrame(rows)
|
||||
return out.sort_values("available_ts").reset_index(drop=True) if not out.empty else out
|
||||
|
||||
|
||||
def add_zone_ladder(zones: pd.DataFrame) -> pd.DataFrame:
|
||||
"""标注每个中枢相对前一个中枢是否同向推进(缠论「趋势 vs 盘整」)。
|
||||
|
||||
z_above / z_below 分别对应向上、向下推进。买信号要求 z_above、卖信号要求 z_below
|
||||
时,30m/2h 的 PF 从 2.72 升到 3.41。
|
||||
"""
|
||||
out = zones.copy()
|
||||
if out.empty:
|
||||
out["z_above"], out["z_below"] = pd.Series(dtype=bool), pd.Series(dtype=bool)
|
||||
return out
|
||||
pg, pdn = out["zg"].shift(), out["zd"].shift()
|
||||
out["z_above"] = (out["zd"] > pg).fillna(False)
|
||||
out["z_below"] = (out["zg"] < pdn).fillna(False)
|
||||
return out
|
||||
|
||||
|
||||
def htf_fx_timeline(chan_htf, df_htf: pd.DataFrame | None = None) -> pd.DataFrame:
|
||||
"""把大级别分型压成一条按确认时间排序的时间线。
|
||||
|
||||
confirm_ts 是该分型最早可被使用的时刻。timestamp 是K线开盘时刻,而分型要等这根K线
|
||||
收盘才算数,所以整体后移一个大级别周期;否则小级别会提前一整根大级别K线拿到信号。
|
||||
|
||||
只取方向与确认时刻——同向过滤用不到背驰强度,省掉 MACD 面积计算。
|
||||
"""
|
||||
src = chan_htf.dataframe if getattr(chan_htf, "dataframe", None) is not None else df_htf
|
||||
if src is None or len(src) == 0:
|
||||
return pd.DataFrame(columns=["confirm_ts", "fx_ts", "direction", "price"])
|
||||
ts = timestamps_ms(src)
|
||||
idx_of = {t: i for i, t in enumerate(src["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))}
|
||||
period = int(np.median(np.diff(ts))) if len(ts) > 1 else 0
|
||||
|
||||
rows = []
|
||||
for klc in getattr(chan_htf, "klc_list", []):
|
||||
if klc.fx not in (Chan_FX_TYPE.TOP, Chan_FX_TYPE.BOTTOM):
|
||||
continue
|
||||
if klc.next is None or klc.next.end_klu is None:
|
||||
continue
|
||||
e_key, c_key = str(klc.end_time), str(klc.next.end_klu.time)
|
||||
if e_key not in idx_of or c_key not in idx_of:
|
||||
continue
|
||||
fx_idx, confirm_idx = idx_of[e_key], idx_of[c_key]
|
||||
if confirm_idx <= fx_idx:
|
||||
continue
|
||||
d = 1 if klc.fx == Chan_FX_TYPE.BOTTOM else -1
|
||||
rows.append({
|
||||
"confirm_ts": int(ts[confirm_idx]) + period,
|
||||
"fx_ts": int(ts[fx_idx]),
|
||||
"direction": d,
|
||||
"price": float(klc.low if d == 1 else klc.high),
|
||||
})
|
||||
out = pd.DataFrame(rows)
|
||||
if out.empty:
|
||||
return pd.DataFrame(columns=["confirm_ts", "fx_ts", "direction", "price"])
|
||||
return out.sort_values("confirm_ts").reset_index(drop=True)
|
||||
|
||||
|
||||
def attach_htf_agree(sig: pd.DataFrame, df_ltf: pd.DataFrame, tl: pd.DataFrame) -> pd.DataFrame:
|
||||
"""给每个小级别信号挂上「入场时刻之前最近的大级别分型」是否同向。
|
||||
|
||||
产出 htf_dir(+1 底 / -1 顶)与 htf_agree(1 同向 / 0 反向 / NaN 无可用分型)。
|
||||
"""
|
||||
out = sig.copy()
|
||||
if sig.empty or tl.empty:
|
||||
out["htf_dir"] = np.nan
|
||||
out["htf_agree"] = np.nan
|
||||
return out
|
||||
|
||||
ts_ltf = timestamps_ms(df_ltf)
|
||||
entry_ts = ts_ltf[out["entry_idx"].to_numpy().astype(int)]
|
||||
k = np.searchsorted(tl["confirm_ts"].to_numpy(), entry_ts, side="right") - 1
|
||||
valid = k >= 0
|
||||
k_safe = np.clip(k, 0, len(tl) - 1)
|
||||
|
||||
fx_dir = tl["direction"].to_numpy()[k_safe].astype(float)
|
||||
out["htf_dir"] = np.where(valid, fx_dir, np.nan)
|
||||
out["htf_agree"] = np.where(
|
||||
valid, (fx_dir == out["direction"].to_numpy()).astype(float), np.nan
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def attach_zone_ladder(sig: pd.DataFrame, zones: pd.DataFrame) -> pd.DataFrame:
|
||||
"""按信号方向取该中枢的阶梯标记:买看 z_above、卖看 z_below。
|
||||
|
||||
zone_i 是 find_fast_bsp3 内 enumerate 出的位置序号,故用 iloc 定位。
|
||||
"""
|
||||
out = sig.copy()
|
||||
if sig.empty:
|
||||
out["ladder_ok"] = pd.Series(dtype=bool)
|
||||
return out
|
||||
z = zones if "z_above" in zones.columns else add_zone_ladder(zones)
|
||||
above = z["z_above"].to_numpy()
|
||||
below = z["z_below"].to_numpy()
|
||||
zi = out["zone_i"].to_numpy().astype(int)
|
||||
ok = np.where(out["direction"].to_numpy() == 1, above[zi], below[zi])
|
||||
out["ladder_ok"] = ok.astype(bool)
|
||||
return out
|
||||
|
||||
|
||||
def find_fast_bsp3(
|
||||
df: pd.DataFrame,
|
||||
zones: pd.DataFrame,
|
||||
scan: int = 200,
|
||||
pullback_win: int = 30,
|
||||
tol: float = -1.0,
|
||||
max_per_zone: int = 1,
|
||||
diag: dict | None = None,
|
||||
require_touch: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""扫描每个中枢,找突破后回抽不回中枢的入场点。
|
||||
|
||||
zones 需含 zg / zd / available_ts,且 available_ts 已是可用时刻。
|
||||
max_per_zone > 1 时,同一中枢在首次入场后继续往后找二次、三次突破回抽,
|
||||
用来检验「趋势里同一中枢反复给机会」是否值得做。
|
||||
|
||||
tol 是「算作回抽中」的边界容差。require_touch=False 时它不再是入场门槛,
|
||||
仅决定哪些K线被视为回抽中而跳过转强判定,故 tol 越大入场越晚。
|
||||
默认负值 = 完全禁用该跳过,突破后每根都检查转强,滞后压到 2.2 根。
|
||||
step34 实测滞后与收益严格单调:9.9根 PF1.88 / 5.8根 2.37 / 2.2根 2.86。
|
||||
|
||||
返回列:
|
||||
entry_idx 实时可下单的K线
|
||||
direction +1 三买 / -1 三卖
|
||||
bo_idx 突破根
|
||||
pb_idx 回抽极值根
|
||||
lag entry_idx - bo_idx
|
||||
depth 回抽深度(相对中枢边界,负值表示曾插入中枢)
|
||||
occ 这是该中枢的第几次入场
|
||||
"""
|
||||
if zones.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
ts = df["timestamp"].to_numpy()
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
high = df["high"].to_numpy(dtype=float)
|
||||
low = df["low"].to_numpy(dtype=float)
|
||||
n = len(df)
|
||||
rows = []
|
||||
|
||||
def note(key: str) -> None:
|
||||
if diag is not None:
|
||||
diag[key] = diag.get(key, 0) + 1
|
||||
|
||||
for zone_i, (_, z) in enumerate(zones.iterrows()):
|
||||
note("中枢总数")
|
||||
zg, zd = float(z["zg"]), float(z["zd"])
|
||||
if zg <= zd:
|
||||
note("×无效中枢")
|
||||
continue
|
||||
start = int(np.searchsorted(ts, z["available_ts"], side="left"))
|
||||
if start >= n - 2:
|
||||
note("×中枢太靠后")
|
||||
continue
|
||||
# 允许多次入场时按比例放宽扫描窗口,否则后几次机会会被窗口截断
|
||||
scan_end = min(start + scan * max_per_zone, n)
|
||||
cursor = start
|
||||
|
||||
for occ in range(1, max_per_zone + 1):
|
||||
if cursor >= n - 2:
|
||||
break
|
||||
# 第一步:找突破。要求突破前确实待在中枢内,避免把远处的价格当突破。
|
||||
was_inside = False
|
||||
bo_idx, d = None, 0
|
||||
for j in range(cursor, scan_end):
|
||||
c = close[j]
|
||||
if zd <= c <= zg:
|
||||
was_inside = True
|
||||
continue
|
||||
if not was_inside:
|
||||
continue
|
||||
bo_idx, d = j, (1 if c > zg else -1)
|
||||
break
|
||||
if bo_idx is None:
|
||||
if occ == 1:
|
||||
note("×窗口内未突破")
|
||||
break
|
||||
|
||||
edge = zg if d == 1 else zd
|
||||
# 第二步:突破后监控回抽,回抽不跌回中枢且重新顺势 -> 入场
|
||||
touched = False
|
||||
pb_idx = None
|
||||
pb_ext = None
|
||||
entry_idx = None
|
||||
fell_back = False
|
||||
for j in range(bo_idx + 1, min(bo_idx + pullback_win + 1, n)):
|
||||
# 收盘跌回中枢 -> 突破失效
|
||||
if zd <= close[j] <= zg:
|
||||
fell_back = True
|
||||
break
|
||||
# 回抽触及边界附近(允许 tol 的毛刺)
|
||||
near = (low[j] <= edge * (1 + tol)) if d == 1 else (high[j] >= edge * (1 - tol))
|
||||
if near:
|
||||
touched = True
|
||||
ext = low[j] if d == 1 else high[j]
|
||||
if pb_ext is None or ((ext < pb_ext) if d == 1 else (ext > pb_ext)):
|
||||
pb_ext, pb_idx = ext, j
|
||||
continue
|
||||
# 回抽后重新顺势:收盘创出前一根之上(三买)/ 之下(三卖)
|
||||
# require_touch=False 时不强求回抽碰到中枢边界,
|
||||
# 这样「突破后一去不回头」的强势段也能收进来。
|
||||
if (touched and pb_idx is not None) or not require_touch:
|
||||
go = close[j] > high[j - 1] if d == 1 else close[j] < low[j - 1]
|
||||
if go:
|
||||
entry_idx = j
|
||||
break
|
||||
if entry_idx is None:
|
||||
if occ == 1:
|
||||
note("×突破后跌回中枢" if fell_back
|
||||
else "×回抽未触及边界" if not touched
|
||||
else "×触及边界但未转强")
|
||||
# 这次突破没走成,从突破点之后继续找下一次
|
||||
cursor = bo_idx + 1
|
||||
continue
|
||||
if pb_ext is None:
|
||||
# 未触及边界就转强(require_touch=False):取突破至入场间的实际极值
|
||||
seg = slice(bo_idx + 1, entry_idx + 1)
|
||||
pb_ext = float(low[seg].min() if d == 1 else high[seg].max())
|
||||
pb_idx = int((low[seg].argmin() if d == 1 else high[seg].argmax())
|
||||
+ bo_idx + 1)
|
||||
if occ == 1:
|
||||
note("√成交")
|
||||
|
||||
# 回抽深度:>0 表示未插入中枢,越大表示回抽越浅
|
||||
depth = (pb_ext - zg) / zg if d == 1 else (zd - pb_ext) / zd
|
||||
rows.append({
|
||||
"entry_idx": entry_idx, "direction": d,
|
||||
"bo_idx": bo_idx, "pb_idx": pb_idx,
|
||||
"lag": entry_idx - bo_idx,
|
||||
"depth": depth,
|
||||
"zg": zg, "zd": zd,
|
||||
"width_pct": (zg - zd) / close[bo_idx],
|
||||
"occ": occ,
|
||||
"zone_i": zone_i,
|
||||
})
|
||||
cursor = entry_idx + 1
|
||||
|
||||
out = pd.DataFrame(rows)
|
||||
if out.empty:
|
||||
return out
|
||||
# 相邻中枢可能突破到同一根K线,同一时刻只能有一个仓位,保留最早成型的那个
|
||||
return (out.sort_values(["entry_idx", "occ", "zone_i"])
|
||||
.drop_duplicates("entry_idx", keep="first")
|
||||
.reset_index(drop=True))
|
||||
+52
-21
@@ -15,10 +15,12 @@ class ChanBI():
|
||||
self.sure_time = None
|
||||
self.klc_list = []
|
||||
self.klc_list.append(klc)
|
||||
self._klc_idx = {klc.index}
|
||||
self.end_time = klc.end_time
|
||||
self.start_time = klc.start_time
|
||||
self.macd_hist = 0
|
||||
self.macd_div = 0
|
||||
self._macd_hist = 0
|
||||
self._macd_div = 0
|
||||
self._macd_dirty = False
|
||||
self.seg = None
|
||||
self.height = 0
|
||||
self.width = 0
|
||||
@@ -33,26 +35,57 @@ class ChanBI():
|
||||
def set_seg(self, seg):
|
||||
self.seg = seg
|
||||
self.seg_index = len(seg.bi_list)-1
|
||||
# macd_hist / macd_div 改为惰性。原来 add_klc 每加一根 KLC 就把整笔的
|
||||
# 所有 KLU 重新累加一遍,是 O(k²);而这两个值只有背驰判定(bsp.py)在读,
|
||||
# lean 模式下 bsp 根本不算,等于全程白算。这里只标脏,取值时才算。
|
||||
# 语义不变:klc_list 只增不减(set_start_klc 会重置并同时清脏),
|
||||
# dir 在 klc 累加期间固定,所以延后到读取时算与逐次重算结果相同。
|
||||
@property
|
||||
def macd_hist(self):
|
||||
if self._macd_dirty:
|
||||
self.cal_macdhist()
|
||||
return self._macd_hist
|
||||
|
||||
@macd_hist.setter
|
||||
def macd_hist(self, v):
|
||||
self._macd_hist = v
|
||||
self._macd_dirty = False
|
||||
|
||||
@property
|
||||
def macd_div(self):
|
||||
self.cal_macd_div()
|
||||
return self._macd_div
|
||||
|
||||
@macd_div.setter
|
||||
def macd_div(self, v):
|
||||
self._macd_div = v
|
||||
|
||||
def set_macdhist(self, macd_hist):
|
||||
self.macd_hist = macd_hist
|
||||
def set_macd_div(self, macd_div):
|
||||
self.macd_div = macd_div
|
||||
def cal_macd_div(self):
|
||||
self.macd_div = 0.0
|
||||
self._macd_div = 0.0
|
||||
if self.pre and self.pre.pre:
|
||||
if self.pre.pre.macd_hist == 0:
|
||||
self.macd_div = 0.0
|
||||
self._macd_div = 0.0
|
||||
else:
|
||||
self.macd_div = self.macd_hist / self.pre.pre.macd_hist
|
||||
self._macd_div = self.macd_hist / self.pre.pre.macd_hist
|
||||
#print(self.start_time, self.end_time, self.macd_hist, self.pre.pre.macd_hist, self.macd_div)
|
||||
def cal_macdhist(self):
|
||||
self.macd_hist = 0
|
||||
self._macd_dirty = False
|
||||
self._macd_hist = 0
|
||||
up = self.dir == Chan_BI_DIR.UP
|
||||
acc = 0
|
||||
for klc in self.klc_list:
|
||||
for klu in klc.klu_list:
|
||||
if self.dir == Chan_BI_DIR.UP and klu.macdhist > 0:
|
||||
self.macd_hist += klu.macdhist
|
||||
if self.dir == Chan_BI_DIR.DOWN and klu.macdhist < 0:
|
||||
self.macd_hist -= klu.macdhist
|
||||
h = klu.macdhist
|
||||
if up:
|
||||
if h > 0:
|
||||
acc += h
|
||||
elif h < 0:
|
||||
acc -= h
|
||||
self._macd_hist = acc
|
||||
def check_bi_zs_overlap(self):
|
||||
if self.next and self.next.next:
|
||||
if self.dir == Chan_BI_DIR.UP:
|
||||
@@ -95,6 +128,10 @@ class ChanBI():
|
||||
self.start_klc = klc
|
||||
self.klc_list = []
|
||||
self.klc_list.append(klc)
|
||||
# klc_list 被整个换掉,去重集合与惰性缓存都要跟着重置,
|
||||
# 否则后续 add_klc 会以为旧下标还在里面而漏加
|
||||
self._klc_idx = {klc.index}
|
||||
self._macd_dirty = True
|
||||
self.high = klc.high
|
||||
self.low = klc.low
|
||||
self.dir = ddir
|
||||
@@ -103,20 +140,14 @@ class ChanBI():
|
||||
def set_next(self, bi):
|
||||
self.next = bi
|
||||
def add_klc(self, klc):
|
||||
added = False
|
||||
if len(self.klc_list) > 0:
|
||||
for index in range(0, len(self.klc_list)):
|
||||
if self.klc_list[index].index == klc.index:
|
||||
added = True
|
||||
break
|
||||
if not added:
|
||||
# 去重原本是对 klc_list 线性扫描,配合下面每次全量重算的 macdhist,
|
||||
# 让「往一笔里加 k 根 KLC」变成 O(k²)。改用下标集合,O(1)。
|
||||
if klc.index not in self._klc_idx:
|
||||
self._klc_idx.add(klc.index)
|
||||
self.klc_list.append(klc)
|
||||
#print(self.start_time, klc.start_time)
|
||||
#print(klc.end_time, klc.index)
|
||||
self.end_klc = klc
|
||||
self.end_time = klc.klu_list[-1].time
|
||||
self.cal_macdhist()
|
||||
self.cal_macd_div()
|
||||
self._macd_dirty = True
|
||||
def append_klc_list(self, klc_list):
|
||||
self.klc_list.append(klc_list)
|
||||
def get_decimal(self, value):
|
||||
|
||||
@@ -281,6 +281,10 @@ class Chan_BSP_TYPE(Enum):
|
||||
S1 = auto()
|
||||
S2 = auto()
|
||||
S3 = auto()
|
||||
# 第四类:三类买卖点的低滞后变体,几何位置同 B3/S3,但不等笔确认。
|
||||
# 见 chanlun/analysis/fast_bsp.py
|
||||
B4 = auto()
|
||||
S4 = auto()
|
||||
NONE = auto()
|
||||
"""
|
||||
class Chan_BSP_TYPE(Enum):
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from chanlun.core.ChanEnum import Chan_BSP_DIR, Chan_BSP_TYPE
|
||||
|
||||
|
||||
class ChanFastBSP():
|
||||
"""第四类买卖点(B4/S4)。
|
||||
|
||||
与 ChanBSP 的区别在于它不挂在笔上:fast_bsp 刻意不等笔确认,入场点是一根具体的
|
||||
K线而非一笔的端点,所以时间与价格直接取自 K 线,没有 bi / klc 可依附。
|
||||
|
||||
htf_agree 与 ladder_ok 是两个独立的过滤标志,不在这里合成——上层(图表或策略)
|
||||
自己决定要不要用、怎么组合。
|
||||
"""
|
||||
|
||||
def __init__(self, time, price, ddir, entry_idx, bo_time=None, pb_time=None,
|
||||
lag=0, depth=0.0, zg=None, zd=None, occ=1,
|
||||
htf_dir=None, htf_agree=None, ladder_ok=None):
|
||||
self.time = time
|
||||
self.price = float(price)
|
||||
self.dir = ddir
|
||||
self.type = Chan_BSP_TYPE.B4 if ddir == Chan_BSP_DIR.BUY else Chan_BSP_TYPE.S4
|
||||
self.entry_idx = int(entry_idx)
|
||||
self.bo_time = bo_time
|
||||
self.pb_time = pb_time
|
||||
self.lag = int(lag)
|
||||
self.depth = float(depth)
|
||||
self.zg = float(zg) if zg is not None else None
|
||||
self.zd = float(zd) if zd is not None else None
|
||||
self.occ = int(occ)
|
||||
self.htf_dir = htf_dir
|
||||
self.htf_agree = htf_agree
|
||||
self.ladder_ok = ladder_ok
|
||||
# 入场即成立,没有「等待确认」这个状态;留此字段是为了与 ChanBSP 的序列化对齐
|
||||
self.is_sure = True
|
||||
self.start_time = time
|
||||
self.end_time = time
|
||||
self.sure_time = time
|
||||
|
||||
def __repr__(self):
|
||||
name = str(self.type).replace('Chan_BSP_TYPE.', '')
|
||||
return f"<ChanFastBSP {name} {self.time} {self.price} lag={self.lag}>"
|
||||
+32
-8
@@ -63,10 +63,11 @@ class ChanKLC():
|
||||
self.bsp = False
|
||||
self.bsp_type = Chan_BSP_TYPE.NONE
|
||||
# EMA状态字典:key为EMA名称,value为 {'pos': Chan_EMA_POS, 'semantic': Chan_EMA_SEMANTIC}
|
||||
self.ema_status = {}
|
||||
self._ema_status = {}
|
||||
self._ema_status_dirty = False
|
||||
# 向后兼容:保留 ema52_status 和 ema52_pos
|
||||
self.ema52_status = 0
|
||||
self.ema52_pos = Chan_EMA_POS.UNKNOWN
|
||||
self._ema52_status = 0
|
||||
self._ema52_pos = Chan_EMA_POS.UNKNOWN
|
||||
self.bb2633upper = klu.bb2633upper
|
||||
self.bb2633lower = klu.bb2633lower
|
||||
self.bb2633middle = klu.bb2633middle
|
||||
@@ -283,21 +284,44 @@ class ChanKLC():
|
||||
'ema156': self.ema156,
|
||||
'ema208': self.ema208,
|
||||
}
|
||||
self.ema_status = {}
|
||||
self._ema_status_dirty = False
|
||||
self._ema_status = {}
|
||||
for name, value in ema_configs.items():
|
||||
# 按 EMA 值的百分比自动计算阈值
|
||||
threshold = abs(value) * self.threshold_pct if value and self.threshold_pct > 0 else 0
|
||||
pos = ChanKLC.cal_ema_pos(self.high, self.low, self.close, value, threshold)
|
||||
semantic = ChanKLC.cal_ema_semantic(pos, self.dir, self.ema_dir)
|
||||
self.ema_status[name] = {
|
||||
self._ema_status[name] = {
|
||||
'pos': pos,
|
||||
'semantic': semantic,
|
||||
'value': value,
|
||||
'threshold': threshold,
|
||||
}
|
||||
# 向后兼容
|
||||
self.ema52_pos = self.ema_status['ema52']['pos']
|
||||
self.ema52_status = ChanKLC.semantic_to_int(self.ema_status['ema52']['semantic'])
|
||||
self._ema52_pos = self._ema_status['ema52']['pos']
|
||||
self._ema52_status = ChanKLC.semantic_to_int(self._ema_status['ema52']['semantic'])
|
||||
|
||||
# 以下三个改成惰性求值。原来 set_end_klu 每次合并 KLU 都会立刻重算一遍,
|
||||
# 实测占 TF_DF 构建的约 25%,而全仓(含前端)没有任何地方读取它的产出。
|
||||
# 保留属性形式是为了任何外部读取仍拿到正确值,只是推迟到真被读时才算。
|
||||
@property
|
||||
def ema_status(self):
|
||||
if self._ema_status_dirty:
|
||||
self.cal_all_ema_status()
|
||||
return self._ema_status
|
||||
|
||||
@property
|
||||
def ema52_pos(self):
|
||||
if self._ema_status_dirty:
|
||||
self.cal_all_ema_status()
|
||||
return self._ema52_pos
|
||||
|
||||
@property
|
||||
def ema52_status(self):
|
||||
if self._ema_status_dirty:
|
||||
self.cal_all_ema_status()
|
||||
return self._ema52_status
|
||||
|
||||
def get_ema_pos(self, ema_name):
|
||||
"""获取指定EMA的客观位置,如 klc.get_ema_pos('ema24')"""
|
||||
if ema_name in self.ema_status:
|
||||
@@ -448,7 +472,7 @@ class ChanKLC():
|
||||
klu.set_klc(self)
|
||||
self.klc_dir = Chan_KLINE_DIR.UP if self.close > self.open else Chan_KLINE_DIR.DOWN
|
||||
self.cal_indicators()
|
||||
self.cal_all_ema_status()
|
||||
self._ema_status_dirty = True
|
||||
if self.open > self.high:
|
||||
self.open = self.high
|
||||
if self.close > self.high:
|
||||
|
||||
+33
-20
@@ -160,27 +160,40 @@ class ChanKLU:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
# 属性名 ← dataframe 列名。两条赋值路径(逐行 Series / 预取 ndarray)共用这张表,
|
||||
# 免得将来加指标时只改一处、另一处静默漏掉。
|
||||
INDICATOR_FIELDS = (
|
||||
('macd', 'macd'), ('signal', 'macdsignal'), ('macdhist', 'macdhist'),
|
||||
('ema26', 'ema26'), ('ema52', 'ema52'), ('ema24', 'ema24'),
|
||||
('ema104', 'ema104'), ('ema156', 'ema156'), ('ema208', 'ema208'),
|
||||
('ema13', 'ema13'), ('ema7', 'ema7'), ('ema5', 'ema5'),
|
||||
('rsi', 'rsi'), ('volume_ratio', 'volume_ratio'),
|
||||
('bb52upper', 'bb52upper'), ('bb52lower', 'bb52lower'),
|
||||
('bb2633upper', 'bb2633upper'), ('bb2633lower', 'bb2633lower'),
|
||||
('bb2633middle', 'bb2633middle'), ('ma5', 'ma5'),
|
||||
)
|
||||
|
||||
def set_indicators(self, item):
|
||||
self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0
|
||||
self.signal = float(item['macdsignal']) if 'macdsignal' in item and item['macdsignal'] else 0
|
||||
self.macdhist = float(item['macdhist']) if 'macdhist' in item and item['macdhist'] else 0
|
||||
self.ema26 = float(item['ema26']) if 'ema26' in item and item['ema26'] else 0
|
||||
self.ema52 = float(item['ema52']) if 'ema52' in item and item['ema52'] else 0
|
||||
self.ema24 = float(item['ema24']) if 'ema24' in item and item['ema24'] else 0
|
||||
self.ema104 = float(item['ema104']) if 'ema104' in item and item['ema104'] else 0
|
||||
self.ema156 = float(item['ema156']) if 'ema156' in item and item['ema156'] else 0
|
||||
self.ema208 = float(item['ema208']) if 'ema208' in item and item['ema208'] else 0
|
||||
self.ema13 = float(item['ema13']) if 'ema13' in item and item['ema13'] else 0
|
||||
self.ema7 = float(item['ema7']) if 'ema7' in item and item['ema7'] else 0
|
||||
self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0
|
||||
self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0
|
||||
self.bb52upper = float(item['bb52upper']) if 'bb52upper' in item and item['bb52upper'] else 0
|
||||
self.bb52lower = float(item['bb52lower']) if 'bb52lower' in item and item['bb52lower'] else 0
|
||||
self.bb2633upper = float(item['bb2633upper']) if 'bb2633upper' in item and item['bb2633upper'] else 0
|
||||
self.bb2633lower = float(item['bb2633lower']) if 'bb2633lower' in item and item['bb2633lower'] else 0
|
||||
self.bb2633middle = float(item['bb2633middle']) if 'bb2633middle' in item and item['bb2633middle'] else 0
|
||||
self.ma5 = float(item['ma5']) if 'ma5' in item and item['ma5'] else 0
|
||||
self.ema5 = float(item['ema5']) if 'ema5' in item and item['ema5'] else 0
|
||||
"""单根赋值。增量追加时每次只有一根,走这条即可。
|
||||
|
||||
原写法是 `float(item[c]) if c in item and item[c] else 0`。其中的真值判断
|
||||
是空转:值为 0.0 时 float(0.0) 仍是 0,值为 NaN 时 NaN 为真值、照样透传。
|
||||
唯一起作用的是「列不存在则填 0」,所以这里只保留那一层。
|
||||
"""
|
||||
for attr, col in self.INDICATOR_FIELDS:
|
||||
v = item[col] if col in item else 0
|
||||
setattr(self, attr, float(v) if v else 0)
|
||||
|
||||
def set_indicators_from(self, cols, i):
|
||||
"""从预取的 {列名: ndarray} 按下标赋值,语义与 set_indicators 相同。
|
||||
|
||||
全量构建时用这条:避免每根 `df.iloc[i]` 构造一个 Series,再在其上做
|
||||
几十次逐键查找——那是 TF_DF 构建 96% 的耗时所在。
|
||||
"""
|
||||
for attr, col in self.INDICATOR_FIELDS:
|
||||
arr = cols.get(col)
|
||||
v = arr[i] if arr is not None else 0
|
||||
setattr(self, attr, float(v) if v else 0)
|
||||
def cal_macd_state(self):
|
||||
# 按定义精简实现:优先级 CROSS0 > 位置(HIGH/HE/RETURN_ZERO) > NEAR0 > UNKNOWN
|
||||
# 首条或缺前一根
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
"""Drop-in replacement for the `talib.abstract` calls this project makes.
|
||||
|
||||
Same call signatures, same column names, same NaN warm-up lengths, so call
|
||||
sites only change their import line.
|
||||
|
||||
Only what the codebase actually uses is implemented: SMA, MA, EMA, RSI, ATR,
|
||||
MACD, BBANDS. Numerical agreement with TA-Lib is enforced by
|
||||
`chanlun/tests/test_ta_compat.py`, which skips when talib is absent.
|
||||
|
||||
The warm-up conventions below are TA-Lib's, not the textbook ones, and they
|
||||
differ between functions — getting them wrong shifts every downstream Chan
|
||||
structure by a bar:
|
||||
|
||||
SMA/BBANDS first value at index period-1
|
||||
EMA seeded with the SMA of the first `period` values, at index period-1
|
||||
RSI/ATR Wilder smoothing (alpha = 1/period), first value at index period
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
__all__ = ["SMA", "MA", "EMA", "RSI", "ATR", "MACD", "BBANDS"]
|
||||
|
||||
|
||||
def _series(data, price: str = "close") -> pd.Series:
|
||||
"""Accept the abstract-API shapes: DataFrame, Series, or ndarray."""
|
||||
if isinstance(data, pd.DataFrame):
|
||||
return data[price].astype(float)
|
||||
if isinstance(data, pd.Series):
|
||||
return data.astype(float)
|
||||
return pd.Series(np.asarray(data, dtype=float))
|
||||
|
||||
|
||||
def _recursive(values: np.ndarray, seed: float, start: int, alpha: float, n: int) -> np.ndarray:
|
||||
"""out[start] = seed; out[i] = alpha*values[i] + (1-alpha)*out[i-1].
|
||||
|
||||
Delegates the recursion to pandas' C implementation rather than a Python
|
||||
loop — `research/` runs this over long histories.
|
||||
"""
|
||||
out = np.full(n, np.nan)
|
||||
if start >= n:
|
||||
return out
|
||||
tail = values[start:].astype(float).copy()
|
||||
tail[0] = seed
|
||||
out[start:] = pd.Series(tail).ewm(alpha=alpha, adjust=False).mean().to_numpy()
|
||||
return out
|
||||
|
||||
|
||||
def SMA(data, timeperiod: int = 30, price: str = "close") -> pd.Series:
|
||||
s = _series(data, price)
|
||||
return s.rolling(window=timeperiod, min_periods=timeperiod).mean()
|
||||
|
||||
|
||||
def MA(data, timeperiod: int = 30, matype: int = 0, price: str = "close") -> pd.Series:
|
||||
if matype != 0:
|
||||
raise NotImplementedError(f"MA matype={matype} is not used by this codebase")
|
||||
return SMA(data, timeperiod, price=price)
|
||||
|
||||
|
||||
def _ema(x: np.ndarray, period: int, start: int) -> np.ndarray:
|
||||
"""EMA whose first output lands on `start`, seeded by the SMA of the
|
||||
`period` values ending there.
|
||||
|
||||
`start` is a parameter because MACD needs the fast EMA to begin later than
|
||||
it naturally would; see the note in MACD().
|
||||
"""
|
||||
n = x.size
|
||||
if n <= start or start < period - 1:
|
||||
return np.full(n, np.nan)
|
||||
seed = x[start - period + 1: start + 1].mean()
|
||||
return _recursive(x, seed, start, 2.0 / (period + 1.0), n)
|
||||
|
||||
|
||||
def EMA(data, timeperiod: int = 30, price: str = "close") -> pd.Series:
|
||||
s = _series(data, price)
|
||||
x = s.to_numpy(dtype=float)
|
||||
return pd.Series(_ema(x, timeperiod, timeperiod - 1), index=s.index)
|
||||
|
||||
|
||||
def RSI(data, timeperiod: int = 14, price: str = "close") -> pd.Series:
|
||||
s = _series(data, price)
|
||||
x = s.to_numpy(dtype=float)
|
||||
n = x.size
|
||||
out = np.full(n, np.nan)
|
||||
if n <= timeperiod:
|
||||
return pd.Series(out, index=s.index)
|
||||
|
||||
delta = np.diff(x)
|
||||
gain = np.where(delta > 0.0, delta, 0.0)
|
||||
loss = np.where(delta < 0.0, -delta, 0.0)
|
||||
|
||||
# delta[k] corresponds to bar k+1, so the first `timeperiod` deltas seed bar `timeperiod`.
|
||||
alpha = 1.0 / timeperiod
|
||||
avg_gain = _recursive(gain, gain[:timeperiod].mean(), timeperiod - 1, alpha, n - 1)
|
||||
avg_loss = _recursive(loss, loss[:timeperiod].mean(), timeperiod - 1, alpha, n - 1)
|
||||
|
||||
ag = avg_gain[timeperiod - 1:]
|
||||
al = avg_loss[timeperiod - 1:]
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
rsi = np.where(al == 0.0, 100.0, 100.0 - 100.0 / (1.0 + ag / al))
|
||||
out[timeperiod:] = rsi
|
||||
return pd.Series(out, index=s.index)
|
||||
|
||||
|
||||
def ATR(data, timeperiod: int = 14) -> pd.Series:
|
||||
if not isinstance(data, pd.DataFrame):
|
||||
raise TypeError("ATR needs a DataFrame with high/low/close")
|
||||
high = data["high"].to_numpy(dtype=float)
|
||||
low = data["low"].to_numpy(dtype=float)
|
||||
close = data["close"].to_numpy(dtype=float)
|
||||
n = high.size
|
||||
out = np.full(n, np.nan)
|
||||
if n <= timeperiod:
|
||||
return pd.Series(out, index=data.index)
|
||||
|
||||
prev_close = close[:-1]
|
||||
tr = np.maximum.reduce([
|
||||
high[1:] - low[1:],
|
||||
np.abs(high[1:] - prev_close),
|
||||
np.abs(low[1:] - prev_close),
|
||||
])
|
||||
|
||||
# tr[k] is bar k+1; the first `timeperiod` true ranges seed bar `timeperiod`.
|
||||
smoothed = _recursive(tr, tr[:timeperiod].mean(), timeperiod - 1, 1.0 / timeperiod, n - 1)
|
||||
out[timeperiod:] = smoothed[timeperiod - 1:]
|
||||
return pd.Series(out, index=data.index)
|
||||
|
||||
|
||||
def MACD(
|
||||
data,
|
||||
fastperiod: int = 12,
|
||||
slowperiod: int = 26,
|
||||
signalperiod: int = 9,
|
||||
price: str = "close",
|
||||
) -> pd.DataFrame:
|
||||
if slowperiod < fastperiod:
|
||||
fastperiod, slowperiod = slowperiod, fastperiod
|
||||
|
||||
s = _series(data, price)
|
||||
x = s.to_numpy(dtype=float)
|
||||
n = x.size
|
||||
|
||||
macd = np.full(n, np.nan)
|
||||
signal = np.full(n, np.nan)
|
||||
hist = np.full(n, np.nan)
|
||||
empty = pd.DataFrame({"macd": macd, "macdsignal": signal, "macdhist": hist}, index=s.index)
|
||||
|
||||
# Both EMAs emit their first value on the same bar. That makes the slow one
|
||||
# ordinary, but re-seeds the fast one from the SMA of the `fastperiod`
|
||||
# values ending there instead of carrying the recursion forward from bar
|
||||
# fastperiod-1 — the two disagree by ~0.2 on a 100-priced series.
|
||||
macd_start = slowperiod - 1
|
||||
if n <= macd_start:
|
||||
return empty
|
||||
line = _ema(x, fastperiod, macd_start) - _ema(x, slowperiod, macd_start)
|
||||
|
||||
# The signal EMA runs over the MACD line, so everything shifts by another
|
||||
# signalperiod-1 bars, and TA-Lib trims the MACD line to match.
|
||||
valid = line[macd_start:]
|
||||
if valid.size < signalperiod:
|
||||
return empty
|
||||
sig = _recursive(
|
||||
valid, valid[:signalperiod].mean(), signalperiod - 1, 2.0 / (signalperiod + 1.0), valid.size
|
||||
)
|
||||
start = macd_start + signalperiod - 1
|
||||
macd[start:] = line[start:]
|
||||
signal[macd_start:] = sig
|
||||
hist = macd - signal
|
||||
|
||||
return pd.DataFrame({"macd": macd, "macdsignal": signal, "macdhist": hist}, index=s.index)
|
||||
|
||||
|
||||
def BBANDS(
|
||||
data,
|
||||
timeperiod: int = 5,
|
||||
nbdevup: float = 2.0,
|
||||
nbdevdn: float = 2.0,
|
||||
matype: int = 0,
|
||||
price: str = "close",
|
||||
) -> pd.DataFrame:
|
||||
if matype != 0:
|
||||
raise NotImplementedError(f"BBANDS matype={matype} is not used by this codebase")
|
||||
s = _series(data, price)
|
||||
middle = s.rolling(window=timeperiod, min_periods=timeperiod).mean()
|
||||
# TA-Lib uses the population standard deviation.
|
||||
std = s.rolling(window=timeperiod, min_periods=timeperiod).std(ddof=0)
|
||||
return pd.DataFrame(
|
||||
{
|
||||
"upperband": middle + nbdevup * std,
|
||||
"middleband": middle,
|
||||
"lowerband": middle - nbdevdn * std,
|
||||
},
|
||||
index=s.index,
|
||||
)
|
||||
@@ -6,9 +6,7 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
@@ -467,7 +465,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 +583,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
|
||||
|
||||
@@ -6,9 +6,7 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""第四类买卖点(B4/S4)接入 TF_DF。
|
||||
|
||||
判定逻辑全在 chanlun/analysis/fast_bsp.py,这里只负责把引擎的中枢/K线喂进去,
|
||||
再把结果包成 ChanFastBSP。
|
||||
|
||||
刻意不在 init_TF_DF 里默认计算:现有构造路径的开销保持不变,由调用方按需触发。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from chanlun.analysis.fast_bsp import (
|
||||
add_zone_ladder,
|
||||
attach_htf_agree,
|
||||
attach_zone_ladder,
|
||||
ensure_timestamp,
|
||||
find_fast_bsp3,
|
||||
htf_fx_timeline,
|
||||
zones_from_zs_list,
|
||||
)
|
||||
from chanlun.core.ChanEnum import Chan_BSP_DIR
|
||||
from chanlun.core.ChanFastBSP import ChanFastBSP
|
||||
|
||||
# 区间套配对:小级别出中枢与买卖点,大级别只出分型定方向。
|
||||
# 取值来自 research/HANDOFF.md §1.5,是回测里实际用过的组合。
|
||||
FAST_BSP_HTF_PAIR = {
|
||||
'1m': '5m',
|
||||
'5m': '30m',
|
||||
'15m': '1h',
|
||||
'30m': '2h',
|
||||
}
|
||||
|
||||
# 未列入配对表的周期回落到这个倍数
|
||||
FAST_BSP_HTF_FALLBACK_RATIO = 4
|
||||
|
||||
_TF_UNIT_MINUTES = {'m': 1, 'h': 60, 'd': 1440, 'w': 10080}
|
||||
|
||||
|
||||
def timeframe_minutes(tf: str) -> int | None:
|
||||
"""'30m' -> 30,'2h' -> 120。无法解析时返回 None。"""
|
||||
if not tf:
|
||||
return None
|
||||
m = re.fullmatch(r'(\d+)\s*([mhdw])', str(tf).strip().lower())
|
||||
if not m:
|
||||
return None
|
||||
return int(m.group(1)) * _TF_UNIT_MINUTES[m.group(2)]
|
||||
|
||||
|
||||
def resolve_htf(tf: str) -> tuple[str, int] | None:
|
||||
"""给小级别找配套的大级别,返回 (标签, 分钟数)。"""
|
||||
minutes = timeframe_minutes(tf)
|
||||
if minutes is None:
|
||||
return None
|
||||
paired = FAST_BSP_HTF_PAIR.get(str(tf).strip().lower())
|
||||
if paired:
|
||||
return paired, timeframe_minutes(paired)
|
||||
return f'{minutes * FAST_BSP_HTF_FALLBACK_RATIO}m', minutes * FAST_BSP_HTF_FALLBACK_RATIO
|
||||
|
||||
|
||||
class FastBspBuilderMixin:
|
||||
def build_fast_bsp_htf(self, df, timeframe=None):
|
||||
"""对同一份 df 重采样得到大级别,不额外拉数据。
|
||||
|
||||
大级别只用来取分型方向,样本太少就没有过滤意义,故重采样后不足 60 根时放弃。
|
||||
"""
|
||||
tf = timeframe or getattr(self, 'timeframe', None)
|
||||
htf = resolve_htf(tf)
|
||||
ltf_minutes = timeframe_minutes(tf)
|
||||
if htf is None or not ltf_minutes:
|
||||
return None
|
||||
label, minutes = htf
|
||||
if not minutes or len(df) * ltf_minutes < minutes * 60:
|
||||
return None
|
||||
try:
|
||||
from chanlun.pipeline.timeframe import TF_DF
|
||||
|
||||
return TF_DF(df, minutes, label)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def cal_fast_bsp(self, df=None, bi_zs_list=None, htf_chan=None, with_htf=True,
|
||||
timeframe=None, **kw):
|
||||
"""算第四类买卖点,返回 ChanFastBSP 列表。
|
||||
|
||||
bi_zs_list 传入已算好的 pure 笔中枢可免去重复计算。
|
||||
with_htf=False 时跳过大级别构建,只留 ladder_ok 这一个过滤标志。
|
||||
kw 透传给 find_fast_bsp3(scan / pullback_win / tol / require_touch 等)。
|
||||
"""
|
||||
src = df if df is not None else getattr(self, 'dataframe', None)
|
||||
if src is None or len(src) == 0:
|
||||
self.fast_bsp_list = []
|
||||
return self.fast_bsp_list
|
||||
|
||||
src = ensure_timestamp(src)
|
||||
if bi_zs_list is None:
|
||||
bi_zs_list = getattr(self, 'bi_zs_list', None)
|
||||
if not bi_zs_list:
|
||||
bi_zs_list = self.cal_bi_zs_list_pure(self.cal_bi_list(self.get_klc_list(self.cal_kl_data(src))))
|
||||
|
||||
zones = zones_from_zs_list(bi_zs_list, src)
|
||||
if zones.empty:
|
||||
self.fast_bsp_list = []
|
||||
return self.fast_bsp_list
|
||||
|
||||
zones = add_zone_ladder(zones)
|
||||
sig = find_fast_bsp3(src, zones, **kw)
|
||||
if sig.empty:
|
||||
self.fast_bsp_list = []
|
||||
return self.fast_bsp_list
|
||||
|
||||
sig = attach_zone_ladder(sig, zones)
|
||||
|
||||
if with_htf:
|
||||
if htf_chan is None:
|
||||
htf_chan = self.build_fast_bsp_htf(src, timeframe)
|
||||
sig = attach_htf_agree(sig, src, htf_fx_timeline(htf_chan) if htf_chan is not None else pd.DataFrame())
|
||||
else:
|
||||
sig['htf_dir'] = None
|
||||
sig['htf_agree'] = None
|
||||
|
||||
times = src['date'].dt.strftime('%Y-%m-%d %H:%M:%S').to_numpy()
|
||||
close = src['close'].to_numpy(dtype=float)
|
||||
|
||||
out = []
|
||||
for r in sig.itertuples(index=False):
|
||||
entry_idx = int(r.entry_idx)
|
||||
agree = getattr(r, 'htf_agree', None)
|
||||
htf_dir = getattr(r, 'htf_dir', None)
|
||||
out.append(ChanFastBSP(
|
||||
time=times[entry_idx],
|
||||
price=close[entry_idx],
|
||||
ddir=Chan_BSP_DIR.BUY if r.direction == 1 else Chan_BSP_DIR.SELL,
|
||||
entry_idx=entry_idx,
|
||||
bo_time=times[int(r.bo_idx)],
|
||||
pb_time=times[int(r.pb_idx)] if r.pb_idx == r.pb_idx else None,
|
||||
lag=r.lag,
|
||||
depth=r.depth,
|
||||
zg=r.zg,
|
||||
zd=r.zd,
|
||||
occ=r.occ,
|
||||
htf_dir=None if htf_dir is None or htf_dir != htf_dir else int(htf_dir),
|
||||
htf_agree=None if agree is None or agree != agree else bool(agree),
|
||||
ladder_ok=bool(r.ladder_ok),
|
||||
))
|
||||
self.fast_bsp_list = out
|
||||
return out
|
||||
@@ -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 chanlun.pipeline.resample 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)
|
||||
@@ -6,9 +6,8 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from chanlun.indicators import ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
@@ -55,6 +54,12 @@ class IndicatorsBuilderMixin:
|
||||
return None
|
||||
|
||||
def add_indicators(self, df):
|
||||
"""算指标并一次性挂到 df 上。
|
||||
|
||||
这里不逐列 `df['x'] = ...`:那样每一列都触发一次 BlockManager 插入,
|
||||
30 多列的开销比全部 TA 计算本身还大(2001 行实测 TA 合计 2.5ms,
|
||||
逐列赋值 3.6ms)。增量路径每根都要走一遍,这笔开销是白付的。
|
||||
"""
|
||||
fast = 26
|
||||
slow = 52
|
||||
period = 9
|
||||
@@ -76,40 +81,43 @@ class IndicatorsBuilderMixin:
|
||||
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
|
||||
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
|
||||
bbp2633 = (df['close'] - bb2633['lowerband']) / (bb2633['upperband'] - bb2633['lowerband'])
|
||||
df['bb2633upper'] = bb2633['upperband']
|
||||
df['bb2633lower'] = bb2633['lowerband']
|
||||
df['bbp2633'] = bbp2633
|
||||
df['bb2633middle'] = bb2633['middleband']
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
df['bbup365'] = bb365['upperband']
|
||||
df['bblow365'] = bb365['lowerband']
|
||||
df['bbp365'] = bbp365
|
||||
df['bbup120'] = bb120['upperband']
|
||||
df['bblow120'] = bb120['lowerband']
|
||||
df['bbp120'] = bbp120
|
||||
df['bbup30'] = bb30['upperband']
|
||||
df['bblow30'] = bb30['lowerband']
|
||||
df['bbmiddle30'] = bb30_middle # 添加bb30中轨
|
||||
df['bbp30'] = bbp30
|
||||
df['bbup302'] = bb302['upperband']
|
||||
df['bblow302'] = bb302['lowerband']
|
||||
df['bbp302'] = bbp302
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
df['ema5'] = ta.EMA(df, timeperiod=5)
|
||||
df['ema10'] = ta.EMA(df, timeperiod=10)
|
||||
df['ema24'] = ta.EMA(df, timeperiod=24)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
df['ema104'] = ta.EMA(df, timeperiod=104)
|
||||
df['ema156'] = ta.EMA(df, timeperiod=156)
|
||||
df['ema208'] = ta.EMA(df, timeperiod=208)
|
||||
df['ema26'] = ta.EMA(df, timeperiod=26)
|
||||
df['ema13'] = ta.EMA(df, timeperiod=13)
|
||||
df['ema7'] = ta.EMA(df, timeperiod=7)
|
||||
df['rsi'] = ta.RSI(df, timeperiod=14)
|
||||
df['volume_ratio'] = self.cal_volume_ratio(df)
|
||||
return df
|
||||
cols = {
|
||||
'bb2633upper': bb2633['upperband'],
|
||||
'bb2633lower': bb2633['lowerband'],
|
||||
'bbp2633': bbp2633,
|
||||
'bb2633middle': bb2633['middleband'],
|
||||
'atr': ta.ATR(df, timeperiod=14),
|
||||
'bbup365': bb365['upperband'],
|
||||
'bblow365': bb365['lowerband'],
|
||||
'bbp365': bbp365,
|
||||
'bbup120': bb120['upperband'],
|
||||
'bblow120': bb120['lowerband'],
|
||||
'bbp120': bbp120,
|
||||
'bbup30': bb30['upperband'],
|
||||
'bblow30': bb30['lowerband'],
|
||||
'bbmiddle30': bb30_middle,
|
||||
'bbp30': bbp30,
|
||||
'bbup302': bb302['upperband'],
|
||||
'bblow302': bb302['lowerband'],
|
||||
'bbp302': bbp302,
|
||||
'macd': macd['macd'],
|
||||
'macdsignal': macd['macdsignal'],
|
||||
'macdhist': macd['macdhist'],
|
||||
}
|
||||
for _p, _n in ((5, 'ema5'), (10, 'ema10'), (24, 'ema24'), (52, 'ema52'),
|
||||
(104, 'ema104'), (156, 'ema156'), (208, 'ema208'),
|
||||
(26, 'ema26'), (13, 'ema13'), (7, 'ema7')):
|
||||
cols[_n] = ta.EMA(df, timeperiod=_p)
|
||||
cols['rsi'] = ta.RSI(df, timeperiod=14)
|
||||
cols['volume_ratio'] = self.cal_volume_ratio(df)
|
||||
|
||||
# 重复调用(增量路径每根都会)时先摘掉旧列,否则 concat 出重名列。
|
||||
# 摘掉再接回末尾,列序与逐列覆盖的结果一致。
|
||||
new = pd.DataFrame(cols, index=df.index)
|
||||
dup = [c for c in new.columns if c in df.columns]
|
||||
if dup:
|
||||
df = df.drop(columns=dup)
|
||||
return pd.concat([df, new], axis=1)
|
||||
|
||||
def get_ema_state(self, dataframe):
|
||||
klu_list = self.get_klu_list(dataframe)
|
||||
|
||||
@@ -6,9 +6,7 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
@@ -86,7 +84,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 +98,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:
|
||||
@@ -114,126 +128,138 @@ class KlineBuilderMixin:
|
||||
return Chan_FX_TYPE.UNKNOWN
|
||||
|
||||
def check_fx_pattern(self, klc):
|
||||
"""给分型前后三根 KLC 的裸 K 打 pattern 标记。
|
||||
|
||||
原本还会把 `klu.to_string()` 拼成一个字符串——那是给下面那行注释掉的
|
||||
print 用的,拼完就丢。它在 cal_bi_list 的内层,2000 根上要跑近三万次
|
||||
f-string + 六万次 enum 格式化,是纯废动作,已删。
|
||||
|
||||
`klu.pattern` 只被 cal_klu_pattern 自己的双 K / 三 K 判定读,
|
||||
不出这个模块,也不进 web 序列化。所以 lean 下整个调用可跳。
|
||||
"""
|
||||
if getattr(self, 'lean', False):
|
||||
return
|
||||
klu_list = klc.pre.klu_list + klc.klu_list + klc.next.klu_list
|
||||
|
||||
self.cal_klu_pattern(klu_list)
|
||||
p = ""
|
||||
for klu in klu_list:
|
||||
p += klu.to_string()
|
||||
#print(p)
|
||||
|
||||
def cal_volume_ratio(self, dataframe, window=10):
|
||||
df = dataframe.copy()
|
||||
# 计算过去N根K线的平均成交量
|
||||
df['avg_volume'] = df['volume'].rolling(window=window).mean()
|
||||
# 计算量比
|
||||
df['volume_ratio'] = df['volume'] / df['avg_volume']
|
||||
# 填充缺失值(前N根K线)
|
||||
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
|
||||
return df['volume_ratio']
|
||||
"""当根量 / 前 window 根均量。前 window-1 根无基准,填 1.0。
|
||||
|
||||
原写法先 `dataframe.copy()` 再挂两列——为算一列 rolling 复制了整张
|
||||
四十列的表。直接在 Series 上算,结果逐值相同。
|
||||
"""
|
||||
vol = dataframe['volume']
|
||||
return (vol / vol.rolling(window=window).mean()).fillna(1.0).rename('volume_ratio')
|
||||
|
||||
def cal_kl_data(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
"""按行构造 KLU 链。
|
||||
|
||||
这里刻意不用 `dataframe.iloc[i]`:那会为每一根新建一个几十列的 Series,
|
||||
随后 set_indicators 再在其上做几十次逐键查找。实测这两件事合计占 TF_DF
|
||||
构建耗时的 96%。改为先把用到的列取成 ndarray,循环里只做整数下标访问。
|
||||
"""
|
||||
n = len(dataframe)
|
||||
if n == 0:
|
||||
return []
|
||||
|
||||
times = self._format_times(dataframe['date'])
|
||||
o_a = dataframe['open'].to_numpy(dtype=float)
|
||||
h_a = dataframe['high'].to_numpy(dtype=float)
|
||||
l_a = dataframe['low'].to_numpy(dtype=float)
|
||||
c_a = dataframe['close'].to_numpy(dtype=float)
|
||||
v_a = dataframe['volume'].to_numpy(dtype=float)
|
||||
|
||||
has_ind = 'macd' in dataframe.columns
|
||||
ind_cols = {}
|
||||
if has_ind:
|
||||
for _attr, col in ChanKLU.INDICATOR_FIELDS:
|
||||
if col in dataframe.columns:
|
||||
ind_cols[col] = dataframe[col].to_numpy(dtype=float)
|
||||
|
||||
klu_list = []
|
||||
last_klu = None
|
||||
for i in range(0, len(dataframe)):
|
||||
item = dataframe.iloc[i]
|
||||
date = item['date']
|
||||
o = item['open']
|
||||
h = item['high']
|
||||
l = item['low']
|
||||
c = item['close']
|
||||
v = item['volume']
|
||||
# time_obj = date.fromtimestamp(date)
|
||||
# date = date + timedelta(hours=8)
|
||||
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
item_data = [
|
||||
time_str,
|
||||
o,
|
||||
h,
|
||||
l,
|
||||
c,
|
||||
v
|
||||
]
|
||||
# klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
|
||||
klu = ChanKLU(time_str, o, h, l, c, v)
|
||||
# print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
|
||||
for i in range(n):
|
||||
klu = ChanKLU(times[i], o_a[i], h_a[i], l_a[i], c_a[i], v_a[i])
|
||||
klu.set_idx(i)
|
||||
klu_list.append(klu)
|
||||
if last_klu:
|
||||
last_klu.set_next(klu)
|
||||
klu.set_pre(last_klu)
|
||||
last_klu = klu
|
||||
if 'macd' in item:
|
||||
klu.set_indicators(item)
|
||||
if has_ind:
|
||||
klu.set_indicators_from(ind_cols, i)
|
||||
return klu_list
|
||||
|
||||
@staticmethod
|
||||
def _format_times(col):
|
||||
"""向量化 strftime。非 datetime 列(少见)退回逐个格式化。"""
|
||||
fmt = '%Y-%m-%d %H:%M:%S'
|
||||
try:
|
||||
return col.dt.strftime(fmt).to_numpy()
|
||||
except AttributeError:
|
||||
return np.array([d.strftime(fmt) for d in col], dtype=object)
|
||||
|
||||
def get_kl_data(self, dataframe:DataFrame):
|
||||
return self.cal_kl_data(dataframe)
|
||||
|
||||
def get_klc_list(self, klu_list):
|
||||
klc_list = []
|
||||
last_klu = None
|
||||
macd = ChanMACD(klu_list)
|
||||
klu_list = macd.cal_macd_state()
|
||||
ema_up_list = []
|
||||
ema_down_list = []
|
||||
ema_up_count = 0
|
||||
ema_down_count = 0
|
||||
last_klu = None
|
||||
for klu in klu_list:
|
||||
ema = klu.ema52
|
||||
last_ema = last_klu.ema52 if last_klu else 0
|
||||
if klu.close >= ema:
|
||||
ema_up_count += 1
|
||||
elif klu.close < ema:
|
||||
ema_down_count += 1
|
||||
if last_klu and last_klu.close >= last_ema and klu.close < ema:
|
||||
ema_up_list.append(ema_up_count)
|
||||
#print(last_klu.time, ema_up_count, "UP END")
|
||||
ema_up_count = 0
|
||||
elif last_klu and last_klu.close < last_ema and klu.close >= ema:
|
||||
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:
|
||||
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.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)
|
||||
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 = []
|
||||
# ChanMACD.__init__ 已调用 cal_macd_state,切勿再调一次(会重复堆积 seg/unittf)。
|
||||
# lean 模式跳过整套 MACD 状态机:它只服务于 bsp/背驰/web 展示,笔与中枢不依赖它。
|
||||
if getattr(self, 'lean', False):
|
||||
self._last_chan_macd = None
|
||||
else:
|
||||
macd = ChanMACD(klu_list)
|
||||
klu_list = macd.klu_list
|
||||
self._last_chan_macd = macd
|
||||
|
||||
last_klu = None
|
||||
for klu in klu_list:
|
||||
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)
|
||||
# cal_trend 只产出 klc.trend,而全仓只有它自己(经 prev_klcs 读自身序列
|
||||
# 状态)、一个 __str__ 和 web 的 klc_trend 图层消费它——笔与中枢不读。
|
||||
# 增量路径的 klc 其 trend 恒为 UNKNOWN 却与批量构建逐字段相同,是实证。
|
||||
# 所以 lean 下可跳;web 走非 lean,图层不受影响。
|
||||
if not getattr(self, 'lean', False):
|
||||
klc_list = self.cal_trend(klc_list)
|
||||
return klc_list
|
||||
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
|
||||
@@ -6,9 +6,7 @@ from decimal import Decimal
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
@@ -355,9 +353,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 +376,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 +395,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 +421,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)
|
||||
|
||||
@@ -17,9 +17,7 @@ from chanlun.core.ChanSBI import ChanSBI
|
||||
from chanlun.core.ChanSEG import ChanSEG
|
||||
from chanlun.core.ChanZS import ChanZS
|
||||
from chanlun.core.ChanBSP import ChanBSP
|
||||
import talib.abstract as ta
|
||||
import pandas as pd
|
||||
from technical.util import resample_to_interval
|
||||
from decimal import Decimal
|
||||
import numpy as np
|
||||
from chanlun.indicators.ChanMACD import ChanMACD
|
||||
@@ -171,6 +169,8 @@ class ChanLun():
|
||||
return self.tf_df.find_second_bsp(bi_list, first_bsp_list)
|
||||
def find_all_bsp(self, bi_list, bi_zs_list):
|
||||
return self.tf_df.find_all_bsp(bi_list, bi_zs_list)
|
||||
def cal_fast_bsp(self, df=None, bi_zs_list=None, htf_chan=None, with_htf=True, timeframe=None, **kw):
|
||||
return self.tf_df.cal_fast_bsp(df, bi_zs_list, htf_chan, with_htf, timeframe, **kw)
|
||||
def get_zs_list(self, bi_list, seg_list):
|
||||
return self.tf_df.get_zs_list(bi_list, seg_list)
|
||||
def cal_bi_zs(self, seg_list):
|
||||
@@ -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):
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""OHLCV resampling — replaces `technical.util.resample_to_interval`.
|
||||
|
||||
That was the only symbol this project imported from `technical`, which in turn
|
||||
pulled in the freqtrade dependency chain. Behaviour is preserved exactly,
|
||||
including the left-labelled bins (rows are candle *open* times) and the
|
||||
`dropna()` that drops empty intervals.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pandas as pd
|
||||
|
||||
__all__ = ["TICKER_INTERVAL_MINUTES", "resample_to_interval"]
|
||||
|
||||
TICKER_INTERVAL_MINUTES: dict[str, int] = {
|
||||
"1m": 1,
|
||||
"5m": 5,
|
||||
"15m": 15,
|
||||
"30m": 30,
|
||||
"1h": 60,
|
||||
"60m": 60,
|
||||
"2h": 120,
|
||||
"4h": 240,
|
||||
"6h": 360,
|
||||
"12h": 720,
|
||||
"1d": 1440,
|
||||
"1w": 10080,
|
||||
}
|
||||
|
||||
_OHLC_AGG = {
|
||||
"open": "first",
|
||||
"high": "max",
|
||||
"low": "min",
|
||||
"close": "last",
|
||||
"volume": "sum",
|
||||
}
|
||||
|
||||
|
||||
def resample_to_interval(dataframe: pd.DataFrame, interval: int | str) -> pd.DataFrame:
|
||||
"""Resample OHLCV rows to `interval` minutes (or a timeframe string).
|
||||
|
||||
Merging the result back onto a finer frame requires care to avoid lookahead
|
||||
bias; this function only resamples.
|
||||
"""
|
||||
if isinstance(interval, str):
|
||||
interval = TICKER_INTERVAL_MINUTES[interval]
|
||||
|
||||
df = dataframe.copy()
|
||||
df = df.set_index(pd.DatetimeIndex(df["date"]))
|
||||
df = df.resample(f"{interval}min", label="left").agg(_OHLC_AGG).dropna()
|
||||
df.reset_index(inplace=True)
|
||||
return df
|
||||
@@ -2,9 +2,8 @@ from datetime import timedelta
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval
|
||||
from chanlun.pipeline.resample import resample_to_interval
|
||||
|
||||
from chanlun.core.ChanBI import ChanBI
|
||||
from chanlun.core.ChanBIZS import ChanBIZS
|
||||
@@ -31,16 +30,26 @@ 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.fast_bsp import FastBspBuilderMixin
|
||||
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):
|
||||
def __init__(self, df=None, interval=0, timeframe=None):
|
||||
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin, FastBspBuilderMixin, IncrementalBuilderMixin):
|
||||
def __init__(self, df=None, interval=0, timeframe=None, lean=False):
|
||||
"""lean=True 只构建到中枢,跳过线段/走势中枢/MACD 状态机。
|
||||
|
||||
研究与实盘只吃 bi_list → 中枢 → fast_bsp 这条链;线段、zs、big_zs 和整套
|
||||
MACD 背驰状态机是 web 展示与 bsp_list 才用的。实测这些占全量构建的约四成。
|
||||
注意 lean 下 bsp_list/seg_list/chanmacd 均为空,**不要给 web 用**。
|
||||
"""
|
||||
self.lean = lean
|
||||
if df is not None:
|
||||
self.init_TF_DF(df, interval, timeframe)
|
||||
def init_TF_DF(self, df, interval, timeframe):
|
||||
self.init_TF_DF(df, interval, timeframe, lean=lean)
|
||||
def init_TF_DF(self, df, interval, timeframe, lean=False):
|
||||
self.lean = lean
|
||||
self.timeframe = timeframe
|
||||
self.interval = interval
|
||||
# 检查 DataFrame 是否为空或没有 date 列
|
||||
@@ -59,17 +68,27 @@ class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilde
|
||||
self.klc_list = []
|
||||
self.bi_list = []
|
||||
self.zs_list = []
|
||||
self.bi_zs_list = []
|
||||
self.bsp_list = []
|
||||
self.fast_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)
|
||||
if self.lean:
|
||||
self.big_zs_list = []
|
||||
self.chanmacd = None
|
||||
return
|
||||
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)
|
||||
self.chanmacd = ChanMACD(self.klu_list)
|
||||
self.klu_list = self.chanmacd.cal_macd_state()
|
||||
# get_klc_list 内已算过 ChanMACD,直接复用
|
||||
self.chanmacd = getattr(self, '_last_chan_macd', None)
|
||||
if self.chanmacd is None:
|
||||
self.chanmacd = ChanMACD(self.klu_list)
|
||||
self.klu_list = self.chanmacd.klu_list
|
||||
|
||||
|
||||
def get_current_klc(self):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import annotations
|
||||
@@ -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()
|
||||
@@ -0,0 +1,198 @@
|
||||
"""Pin chanlun.indicators.ta to TA-Lib's output, bar for bar.
|
||||
|
||||
These indicators feed the Chan structure builders, so a one-bar shift in the
|
||||
warm-up or a different smoothing seed silently changes every downstream
|
||||
bi/seg/zs. Equality against the reference implementation is the only check
|
||||
that catches that.
|
||||
|
||||
Skipped when talib is unavailable — which is the point of the replacement, so
|
||||
the suite still has to pass without it. Run in an environment that has talib
|
||||
whenever chanlun/indicators/ta.py changes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from chanlun.indicators import ta # noqa: E402
|
||||
from chanlun.pipeline.resample import resample_to_interval # noqa: E402
|
||||
|
||||
try:
|
||||
import talib.abstract as reference
|
||||
except ImportError: # pragma: no cover
|
||||
reference = None
|
||||
|
||||
requires_talib = unittest.skipIf(reference is None, "talib not installed")
|
||||
|
||||
|
||||
def make_ohlcv(n: int = 900, seed: int = 7) -> pd.DataFrame:
|
||||
"""Random walk with enough range for BBANDS(365) and EMA(208) to warm up."""
|
||||
rng = np.random.default_rng(seed)
|
||||
close = 100.0 + np.cumsum(rng.normal(0.0, 1.0, n))
|
||||
spread = np.abs(rng.normal(0.0, 0.6, n)) + 0.05
|
||||
high = close + spread
|
||||
low = close - spread
|
||||
open_ = np.concatenate([[close[0]], close[:-1]])
|
||||
return pd.DataFrame(
|
||||
{
|
||||
"date": pd.date_range("2024-01-01", periods=n, freq="1min", tz="UTC"),
|
||||
"open": open_,
|
||||
"high": np.maximum.reduce([high, open_, close]),
|
||||
"low": np.minimum.reduce([low, open_, close]),
|
||||
"close": close,
|
||||
"volume": rng.uniform(1.0, 100.0, n),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TAEquivalence(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.df = make_ohlcv()
|
||||
|
||||
def assertSameSeries(self, got, expected, label: str) -> None:
|
||||
g = np.asarray(got, dtype=float)
|
||||
e = np.asarray(expected, dtype=float)
|
||||
self.assertEqual(g.shape, e.shape, f"{label}: shape")
|
||||
np.testing.assert_array_equal(
|
||||
np.isnan(g), np.isnan(e), err_msg=f"{label}: NaN warm-up differs"
|
||||
)
|
||||
mask = ~np.isnan(e)
|
||||
np.testing.assert_allclose(
|
||||
g[mask], e[mask], rtol=1e-9, atol=1e-8, err_msg=f"{label}: values differ"
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_sma(self) -> None:
|
||||
for period in (5, 20, 90, 250):
|
||||
self.assertSameSeries(
|
||||
ta.SMA(self.df, timeperiod=period),
|
||||
reference.SMA(self.df, timeperiod=period),
|
||||
f"SMA({period})",
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_ma(self) -> None:
|
||||
for period in (5, 10, 250):
|
||||
self.assertSameSeries(
|
||||
ta.MA(self.df, timeperiod=period),
|
||||
reference.MA(self.df, timeperiod=period),
|
||||
f"MA({period})",
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_ema(self) -> None:
|
||||
for period in (5, 7, 10, 13, 24, 26, 30, 52, 104, 156, 208):
|
||||
self.assertSameSeries(
|
||||
ta.EMA(self.df, timeperiod=period),
|
||||
reference.EMA(self.df, timeperiod=period),
|
||||
f"EMA({period})",
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_rsi(self) -> None:
|
||||
for period in (7, 14, 21):
|
||||
self.assertSameSeries(
|
||||
ta.RSI(self.df, timeperiod=period),
|
||||
reference.RSI(self.df, timeperiod=period),
|
||||
f"RSI({period})",
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_atr(self) -> None:
|
||||
for period in (7, 14, 30):
|
||||
self.assertSameSeries(
|
||||
ta.ATR(self.df, timeperiod=period),
|
||||
reference.ATR(self.df, timeperiod=period),
|
||||
f"ATR({period})",
|
||||
)
|
||||
|
||||
@requires_talib
|
||||
def test_macd(self) -> None:
|
||||
for fast, slow, signal in ((12, 26, 9), (26, 52, 9), (5, 35, 5)):
|
||||
got = ta.MACD(self.df, fastperiod=fast, slowperiod=slow, signalperiod=signal)
|
||||
exp = reference.MACD(self.df, fastperiod=fast, slowperiod=slow, signalperiod=signal)
|
||||
for col in ("macd", "macdsignal", "macdhist"):
|
||||
self.assertSameSeries(got[col], exp[col], f"MACD({fast},{slow},{signal}).{col}")
|
||||
|
||||
@requires_talib
|
||||
def test_bbands(self) -> None:
|
||||
cases = (
|
||||
(365, 3.0, 3.0),
|
||||
(120, 3.0, 3.0),
|
||||
(41, 2.3, 2.3),
|
||||
(41, 2.0, 2.0),
|
||||
(26, 3.0, 3.0),
|
||||
(20, 2.0, 2.0),
|
||||
(14, 2.0, 2.0),
|
||||
)
|
||||
for period, up, dn in cases:
|
||||
got = ta.BBANDS(self.df, timeperiod=period, nbdevup=up, nbdevdn=dn, matype=0)
|
||||
exp = reference.BBANDS(self.df, timeperiod=period, nbdevup=up, nbdevdn=dn, matype=0)
|
||||
for col in ("upperband", "middleband", "lowerband"):
|
||||
self.assertSameSeries(got[col], exp[col], f"BBANDS({period},{up},{dn}).{col}")
|
||||
|
||||
@requires_talib
|
||||
def test_bbands_is_more_accurate_than_talib_on_tiny_windows(self) -> None:
|
||||
"""A deliberate divergence, documented so nobody "fixes" it back.
|
||||
|
||||
TA-Lib derives the variance from sumsq/n - mean**2, which cancels
|
||||
catastrophically when the window is short and prices are far from zero;
|
||||
at timeperiod=2 it drifts ~1e-6. Rolling std is accurate there, so the
|
||||
two disagree. No timeperiod below 14 is used in this codebase, and the
|
||||
periods that are used agree to ~1e-10 (covered by test_bbands).
|
||||
"""
|
||||
got = ta.BBANDS(self.df, timeperiod=2, nbdevup=1.0, nbdevdn=1.0, matype=0)["upperband"]
|
||||
exp = reference.BBANDS(self.df, timeperiod=2, nbdevup=1.0, nbdevdn=1.0, matype=0)["upperband"]
|
||||
|
||||
window = self.df["close"].rolling(2)
|
||||
truth = window.mean() + window.std(ddof=0)
|
||||
ours = np.nanmax(np.abs((got - truth).to_numpy()))
|
||||
theirs = np.nanmax(np.abs((exp - truth).to_numpy()))
|
||||
self.assertLess(ours, 1e-9)
|
||||
self.assertLess(ours, theirs)
|
||||
|
||||
@requires_talib
|
||||
def test_matches_on_real_price_scale(self) -> None:
|
||||
"""Guard against tolerances that only hold near 100."""
|
||||
df = self.df.copy()
|
||||
for col in ("open", "high", "low", "close"):
|
||||
df[col] *= 900.0
|
||||
self.assertSameSeries(
|
||||
ta.ATR(df, timeperiod=14), reference.ATR(df, timeperiod=14), "ATR@scale"
|
||||
)
|
||||
self.assertSameSeries(
|
||||
ta.RSI(df, timeperiod=14), reference.RSI(df, timeperiod=14), "RSI@scale"
|
||||
)
|
||||
|
||||
|
||||
class ResampleEquivalence(unittest.TestCase):
|
||||
@unittest.skipIf(
|
||||
__import__("importlib").util.find_spec("technical") is None,
|
||||
"technical not installed",
|
||||
)
|
||||
def test_matches_technical(self) -> None:
|
||||
from technical.util import resample_to_interval as ref_resample
|
||||
|
||||
df = make_ohlcv(600)
|
||||
for interval in (5, 15, 60, "5m", "1h"):
|
||||
got = resample_to_interval(df, interval)
|
||||
exp = ref_resample(df, interval)
|
||||
pd.testing.assert_frame_equal(got, exp, check_exact=False, rtol=1e-12)
|
||||
|
||||
def test_shapes_without_reference(self) -> None:
|
||||
df = make_ohlcv(120)
|
||||
out = resample_to_interval(df, 5)
|
||||
self.assertEqual(list(out.columns), ["date", "open", "high", "low", "close", "volume"])
|
||||
self.assertLessEqual(len(out), 120 // 5 + 1)
|
||||
self.assertTrue((out["high"] >= out["low"]).all())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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 停止服务"
|
||||
@@ -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)。
|
||||
@@ -1,31 +0,0 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 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/`
|
||||
@@ -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 OK;pytest 含 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 再导出符合计划,建议 ECR-002 继续按 data/analyze/serialize 物理拆分。
|
||||
2. **`web/static/js/app/chart_tv.js` ~4664 行** — `initTradingView` 单体;行为冻结下可接受。
|
||||
3. **`/api/analyze` 契约测试偏浅** — 仅关键字段清单 + 路由存在;无固定 fixture 的端到端 JSON 快照(需 mock 行情)。
|
||||
4. **TEST_REPORT 写「5 passed」** — 现为 6(含 shim 兼容测);Release 前可改正文(L0 docs)。
|
||||
5. **L1:`TF_DF.get_zs_list` 恢复** — 合理兼容修复;golden 走 analyze 路径未覆盖 `TF_DF(df,...)` 全量 `__init__`,建议后续加一条 init 冒烟(非阻断)。
|
||||
|
||||
### 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) |
|
||||
@@ -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
|
||||
@@ -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,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 Refactor(1B+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 无 diff;golden 与路由契约通过。
|
||||
|
||||
## 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,3 +0,0 @@
|
||||
# IDEA: 引擎与 Web 结构重整
|
||||
|
||||
根目录与 `web/app.py` / `index.html` 过于臃肿,需在行为冻结前提下包化与分层。见 ECR-001。
|
||||
@@ -1,31 +0,0 @@
|
||||
# IMPLEMENTATION_REPORT — ECR-001
|
||||
|
||||
**Date:** 2026-08-05
|
||||
**Role:** ENGINEER
|
||||
**Status:** Complete
|
||||
|
||||
## Summary
|
||||
|
||||
完成缠论引擎包化、`TF_DF` builders 拆分、Web services/blueprints 分层、前端 `index.html` JS 外置模块化;算法与 `/api/analyze` 契约冻结;`config/` / `strategies/` 无改动。
|
||||
|
||||
## Changes
|
||||
|
||||
| Area | Result |
|
||||
|------|--------|
|
||||
| `chanlun/` | core / pipeline(+builders) / indicators / analysis |
|
||||
| Root shims | `ChanLun.py` `TF_DF.py` `ChanEnum.py` 等再导出 |
|
||||
| L1 fix | 恢复 `TF_DF.get_zs_list` → `get_seg_zs_list` |
|
||||
| Web | `config.py` `services/*` `api/*` `create_app()` |
|
||||
| Frontend | `static/js/app/*`;未接线 TSX/TS → `_unused/` |
|
||||
| Examples/notes | `examples/` `docs/notes/` |
|
||||
| Tests | golden pipeline + analyze contract smoke |
|
||||
|
||||
## Config
|
||||
|
||||
- 代理:`CHAN_HTTP_PROXY` / `HTTP_PROXY`(默认不强制 7897)
|
||||
- `DATA_SERVICE_URL` / `FLASK_PORT`
|
||||
|
||||
## Follow-ups(非本 ECR)
|
||||
|
||||
- (已完成)策略与 web/tests 改为 `from chanlun import ...`;根 shim 仅兼容旧脚本
|
||||
- (已完成)`chart.js` 拆为 format/view/tv/sync/tables;chan_tv 共用 `static/js/app/`
|
||||
@@ -1,22 +0,0 @@
|
||||
# PRODUCT_SPEC: ECR-001 结构重构
|
||||
|
||||
## Goal
|
||||
|
||||
在不改变缠论计算结果与 Web 分析 API 对外语义的前提下,提升代码可维护性。
|
||||
|
||||
## User-visible behavior
|
||||
|
||||
| 项 | 期望 |
|
||||
|----|------|
|
||||
| 图表页 /chan_tv / 首页 | 功能与交互保持 |
|
||||
| `/api/analyze` | 字段兼容;可增不可删 |
|
||||
| Freqtrade 策略 | 无需改 import 路径即可加载引擎 |
|
||||
|
||||
## Non-goals
|
||||
|
||||
- 新买卖点规则、新 UI、新行情后端、策略超参优化
|
||||
|
||||
## Acceptance(产品视角)
|
||||
|
||||
1. 用户打开 Web 图表仍能看到笔/段/中枢/买卖点叠加。
|
||||
2. 既有策略代码不因本次重构而修改。
|
||||
@@ -1,39 +0,0 @@
|
||||
# Project Profile — chan (缠论)
|
||||
|
||||
> Agent 第一次读这个文件。不要重新猜技术栈;偏离见 Forbidden + ADR。
|
||||
|
||||
## Type
|
||||
Trading System(缠论分析引擎 + 可视化 Web;Freqtrade 策略目录独立、本 ECR 不改)
|
||||
|
||||
## Stack Lock
|
||||
|
||||
| Layer | Choice |
|
||||
|-------|--------|
|
||||
| Language | Python 3 |
|
||||
| Engine package | `chanlun/` |
|
||||
| Backend | Flask |
|
||||
| Realtime | 无(请求式分析) |
|
||||
| Database | 无(行情外部 DATA_SERVICE / CCXT / A 股接口) |
|
||||
| Frontend | TradingView Charting Library + 原生 JS |
|
||||
| Deployment | gunicorn / systemd(web) |
|
||||
| Architecture Pattern | 包化引擎 + Web services/blueprints + 根目录兼容 shim |
|
||||
|
||||
## Forbidden(无 ADR / 无 ECR 禁止)
|
||||
|
||||
- 无 ADR 修改笔 / 线段 / 中枢 / 买卖点算法语义
|
||||
- 无 ECR 修改 `config/`、`strategies/` 策略逻辑或参数
|
||||
- 无 ECR 破坏 `/api/analyze` JSON 契约(可增不可删)
|
||||
- 引入 Kafka / MongoDB / 微服务拆分(除非新 ADR)
|
||||
- 本轮引入 Vite/React/TS 构建流水线
|
||||
|
||||
## Active anchors
|
||||
|
||||
- ECR: ECR-001
|
||||
- EXP: N/A(本变更不改交易行为语义)
|
||||
- TRACEABILITY: `docs/TRACEABILITY.md`
|
||||
|
||||
## Pointers
|
||||
|
||||
- Rules: `PROJECT_RULES.md`
|
||||
- Stack detail: `TECH_STACK.md`
|
||||
- Memory: `AGENT_MEMORY.md`(若存在)
|
||||
@@ -1,24 +0,0 @@
|
||||
# Project Rules — chan
|
||||
|
||||
## Scope boundaries
|
||||
|
||||
1. `config/`、`strategies/`:Freqtrade 策略资产,默认只读;任何改动需独立 ECR。
|
||||
2. `chanlun/`:缠论引擎正式包;算法变更需 L2+ ECR + 回归基线。
|
||||
3. 根目录 `Chan*.py` / `TF_DF.py`:兼容 shim,保持 `from ChanLun import ChanLun` 可用。
|
||||
4. `web/`:可视化与 API;契约冻结于 ECR-001。
|
||||
|
||||
## Change levels
|
||||
|
||||
- L0 docs / L1 bugfix / L2 behavior / L3 architecture — 见 ESS CHANGE_MANAGEMENT。
|
||||
- 结构重构默认 L3;若触及缠论识别结果 → 升为 L2 并要求 RISK_REVIEW + EXP。
|
||||
|
||||
## Compatibility
|
||||
|
||||
- 正式导入:`from chanlun import ChanLun, TF_DF`;`from chanlun.core.ChanEnum import ...`
|
||||
- 根目录 `Chan*.py` / `TF_DF.py` 为兼容 shim(旧脚本可用);新代码与 strategies 应使用 `chanlun` 包
|
||||
- Web URL 与 `/api/analyze` 字段名/结构保持兼容
|
||||
- 策略文件通过 `sys.path.append(仓库根)` 保证能找到 `chanlun` 包
|
||||
|
||||
## Language
|
||||
|
||||
- 代码注释与提交说明优先中文。
|
||||
@@ -1,56 +0,0 @@
|
||||
# RELEASE_REPORT — ECR-001 / v1.0.0
|
||||
|
||||
**Role:** RELEASE_MANAGER
|
||||
**Date:** 2026-08-05
|
||||
**System version:** `v1.0.0`(首个正式 Release)
|
||||
**Strategy version:** 不变(本 release 不改策略交易语义;strategies 仅 import 迁移)
|
||||
**Git commit:** `74dec4e` + 本 release 文档提交
|
||||
**Branch:** `dev`
|
||||
|
||||
## Scope
|
||||
|
||||
缠论引擎包化(`chanlun/`)+ Web 分层 + 前端模块化 + ESS 治理文档落盘(ECR-001 L3,行为冻结)。
|
||||
|
||||
## Gates
|
||||
|
||||
| Gate | Status | Evidence |
|
||||
|------|--------|----------|
|
||||
| ECR Approved | PASS | `docs/ECR/ECR-001-chan-web-restructure.md` |
|
||||
| ENGINEERING_SPEC / ADR | PASS | `docs/ENGINEERING_SPEC/` · `docs/ADR/ADR-001-package-layout.md` |
|
||||
| TEST_REPORT | PASS | `docs/TEST_REPORT/ECR-001.md`;golden + pytest |
|
||||
| CODE_REVIEW | PASS Approve | `docs/CODE_REVIEW/ECR-001.md` |
|
||||
| RISK_REVIEW / EXP | N/A | 不改交易决策语义 |
|
||||
| Human release intent | PASS | Human:「作为第一 release 版本」 |
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Acceptance 已由 Reviewer 对照通过
|
||||
- [x] `config/` 无策略参数变更
|
||||
- [x] Rollback:`git revert` / 回到 tag 前 commit;Web 回滚同 tag
|
||||
- [x] 环境:Web Flask(`FLASK_PORT` 默认 8128);行情依赖 `DATA_SERVICE_URL`
|
||||
- [x] Tag:`v1.0.0`
|
||||
|
||||
## Artifacts
|
||||
|
||||
| Kind | Path |
|
||||
|------|------|
|
||||
| CODE_REVIEW | `docs/CODE_REVIEW/ECR-001.md` |
|
||||
| RELEASE | 本文件 |
|
||||
| CHANGELOG | `docs/CHANGELOG/CHANGELOG.md` |
|
||||
|
||||
## Promote note
|
||||
|
||||
本 tag 标记 **系统软件 v1.0.0**(分析引擎 + Web)。
|
||||
**非** Freqtrade Live 交易 Promote;若上 Live 策略仍需独立 Paper/EXP + Human 批准。
|
||||
|
||||
## Rollback
|
||||
|
||||
```bash
|
||||
git checkout v1.0.0 # 钉在本版本
|
||||
# 或回退到上一提交
|
||||
git checkout e2e45bc
|
||||
```
|
||||
|
||||
## Decision
|
||||
|
||||
**Release `v1.0.0`** — ship on `dev` with annotated tag.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user