1 Commits
433 changed files with 41205 additions and 182719 deletions
+28 -30
View File
@@ -1,44 +1,42 @@
# MacOS
.DS_Store
# Python
# Python编译文件和缓存
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
.pytest_cache/
# Logs & databases
# 策略文件的缓存
strategies/__pycache__/
# Machine Learning / AI model files
*_model*_xgb_model.json
*modelchan*.json
*.libsvm
feature_meta
*_model_feature_data.csv
*.pem
# Log files
*.log
# Database files
*.sqlite
*.sqlite-shm
*.sqlite-wal
# 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
.DS_Store
交易记录/~$交易规则.docx
/datasvc/data
.DS_Store
.DS_Store
/data_provider/data
.DS_Store
.DS_Store
.DS_Store
.DS_Store
.DS_Store
.DS_Store
data_provider/._config.json
.gstack/
+33
View File
@@ -0,0 +1,33 @@
# chan — Agent Entry
本仓受 ESS 约束。不要一上来扫全库或加载全部 governance。
## Boot
1. `docs/PROJECT_PROFILE.md`
2. `docs/PROJECT_RULES.md`
3. `docs/STATE/CURRENT.md` + `docs/AGENT_MEMORY.md`
4. 有进行中任务再读 `docs/TASKS/` / 对应 ECR / HANDOFF
5. 角色文件:ESS 根目录 `agents/{ARCHITECT|ENGINEER|REVIEWER|RELEASE_MANAGER}.md`
## Roles(选一)
| 意图 | 角色 |
|------|------|
| 规格 / 架构 / ECR | ARCHITECT |
| 实现 / 修 bug | ENGINEER |
| 审阅 | REVIEWER |
| 发版 / tag | RELEASE_MANAGER |
## Never
- 无 ECR 改 `config/` / `strategies/` 交易逻辑
- 无 ADR 改缠论算法语义
- 无 ECR 删减 `/api/analyze` 字段
- 把聊天记录当成完成;阶段结束须落盘 `docs/`
## Pointers
- TRACEABILITY: `docs/TRACEABILITY.md`
- CHANGELOG: `docs/CHANGELOG/CHANGELOG.md`
- 人类向导:`CLAUDE.md`
+116
View File
@@ -0,0 +1,116 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
缠论 (Chan Theory) technical analysis system for Freqtrade. Implements Chan Zhong Shui Chan's theory for crypto/stock trading, including fractal (分型), stroke (笔), segment (线段), pivot/center (中枢), and buy/sell point (买卖点) detection.
## Governance
- Agent 入口:`AGENTS.md`boot 顺序)· `docs/PROJECT_PROFILE.md` · `docs/AGENT_MEMORY.md` · `docs/STATE/CURRENT.md`
- ESS 文档:`docs/ECR/``docs/ENGINEERING_SPEC/``docs/TRACEABILITY.md``docs/CHANGELOG/`
- **正式引擎包**`chanlun/`strategies / web 已用 `from chanlun import ...`
- 根目录 `Chan*.py` / `TF_DF.py` 仍为 **兼容 shim**(旧脚本可用)
- 变更分级:无 ECR 不改 strategies/config;无 ADR 不改缠论算法语义
## Core Architecture
### Chan Theory Engine (`chanlun/`)
```text
chanlun/
core/ # KLU KLC BI SBI SEG ZS BIZS BSP Enum CTime
pipeline/ # orchestrator(ChanLun) + timeframe(TF_DF) + builders/
indicators/ # ChanMACD*
analysis/ # Zone Classifier Pivot Heng PY Find_Trend ...
```
Data processing pipeline (each step feeds the next):
1. **`chanlun.core.ChanKLU`** — Raw K-line unit with TA indicators and pattern recognition
2. **`chanlun.core.ChanKLC`** — Combined K-line: inclusion + fractal; `.next`/`.pre` linked list
3. **`chanlun.core.ChanBI`** — Stroke (笔)
4. **`chanlun.core.ChanSBI`** — Special stroke → SEG
5. **`chanlun.core.ChanSEG`** — Segment (线段)
6. **`chanlun.core.ChanZS`** / **`ChanBIZS`** — Centers (中枢)
7. **`chanlun.core.ChanBSP`** — Buy/Sell points
8. **`chanlun.pipeline.orchestrator.ChanLun`** — Orchestrator
9. **`chanlun.pipeline.timeframe.TF_DF`** — Timeframe facade;实现拆在 `pipeline/builders/`
### Services
- **外部 DATA_SERVICE** — 行情服务(env: `DATA_SERVICE_URL`);本仓库可不含 data_provider 源码
- **`web/`** — Flask UI`create_app()` + `api/` blueprints + `services/`;前端 `static/js/app/`。默认端口见 `web/config.py``FLASK_PORT`,常见 8128
- **`strategies/`** — Freqtrade strategies(本 ECR 不改)
- **`config/`** — Freqtrade configs(本 ECR 不改)
### Data Flow
```
Exchange / DATA_SERVICE → Freqtrade Strategy / web → ChanLun → TF_DF
→ KLU → KLC → BI → SBI → SEG → ZS → BSP
```
## Common Commands
### Freqtrade Trading
```bash
# Live trade
freqtrade trade -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies
# Backtest
freqtrade backtesting -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies --timerange=20251008-
# Download data
freqtrade download-data -c ./user_data/Chan/config/<config>.json -t 1m 1h 1d --pairs BTC/USDT:USDT --timerange=20240101-
# Hyperopt
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/<config>.json -e 200 --timerange=20250201-20250901
# Plot
freqtrade plot-dataframe --strategy <StrategyName> --datadir user_data/data/binance -c ./user_data/Chan/config/<config>.json --timerange=20250721-
```
### Data Provider
```bash
# Docker
cd data_provider && docker compose up -d
# Direct
cd data_provider && python main.py
# With custom config
CONFIG_PATH=./config.json python main.py
```
### Web UI
```bash
cd web && python app.py
# or via gunicorn:
gunicorn -w 4 -b 0.0.0.0:8123 app:app
# Deploy scripts:
cd web && ./deploy.sh # standard
cd web && ./deploy_venv.sh # Ubuntu 22.04+ (venv)
```
### Docker (Freqtrade)
```bash
sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/<config>.json --strategy <StrategyName> --strategy-path ./user_data/Chan/strategies --timerange=20250721-
```
## Key Conventions
- All Chan theory classes are prefixed with `Chan` (e.g., `ChanBI`, `ChanZS`)
- Strategies import `ChanLun` and add `sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))` to import from parent
- MACD params: `MACD(26, 52, 9)` by default (slow period 52 instead of standard 26)
- Enums in `ChanEnum.py` use `auto()` values
- `ChanKLC` is a linked-list style data structure with `.next`/`.pre` pointers
- The `TF_DF` class is the primary data container per timeframe
- K-line direction uses `Chan_KLINE_DIR` (UP/DOWN/COMBINE/INCLUDED)
- All text comments/commits are in Chinese
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBI import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBIZS import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanBSP import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanCTime import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanEnum import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanHeng import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanKLC import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanKLU import * # noqa: F403
+3
View File
@@ -0,0 +1,3 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.pipeline.orchestrator import ChanLun # noqa: F401
from chanlun.pipeline.timeframe import TF_DF # noqa: F401
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanLun_Classifier import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACD import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDHistSet import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDSeg import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.indicators.ChanMACDUnitTF import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPY import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPivotClassifier import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanPivotMonitor import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanSBI import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanSEG import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.ChanZS import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.ChanZone import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.core.Chan_FX_Box import * # noqa: F403
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.analysis.Find_Trend import * # noqa: F403
-130
View File
@@ -1,130 +0,0 @@
# 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.11pandas 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——这些都**不在**依赖清单里,
是有意为之。要用得自行安装。
+2
View File
@@ -0,0 +1,2 @@
"""兼容 shim — 请优先 from chanlun import ..."""
from chanlun.pipeline.timeframe import TF_DF # noqa: F401
View File
+2
View File
@@ -14,10 +14,12 @@ 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
+3 -4
View File
@@ -2,7 +2,7 @@ import ccxt
import pandas as pd
import numpy as np
import mplfinance as mpf
from chanlun.indicators import ta
from talib import MACD, SMA
from datetime import datetime, timedelta
import logging
import datetime as dt
@@ -249,9 +249,8 @@ def analyze_higher_timeframe(df_30m):
# 8. Back-divergence detection (enhanced)
def detect_back_divergence(df, strokes, higher_trend):
try:
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)
macd, signal, hist = MACD(df['Close'], fastperiod=12, slowperiod=26, signalperiod=9)
sma20 = SMA(df['Close'], timeperiod=20)
df['macd'] = macd
df['hist'] = hist
df['sma20'] = sma20
-361
View File
@@ -1,361 +0,0 @@
"""快速三类买卖点(引擎内称第四类,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
def timestamps_ms(src: pd.DataFrame) -> np.ndarray:
"""取毫秒时间戳。研究侧的 df 自带 timestampweb 侧的不一定,故按 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) -> 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)
def zones_from_zs_list(zs_list, src: pd.DataFrame) -> 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)))
rows = []
for zs in zs_list:
bis = getattr(zs, "bi_list", [])
if not bis:
continue
# 中枢可用时刻:构成它的最后一笔被确认之时
last_bi = bis[-1]
sure_key = str(getattr(last_bi, "sure_time", "") or "")
end_key = str(getattr(last_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_agree1 同向 / 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))
+7
View File
@@ -0,0 +1,7 @@
"""威科夫分析(启发式):交易区间 / 阶段 / 事件 / Volume Profile / Live。"""
from __future__ import annotations
from .engine import analyze_wyckoff
from .live import execution_signal_from_wyckoff
__all__ = ["analyze_wyckoff", "execution_signal_from_wyckoff"]
+196
View File
@@ -0,0 +1,196 @@
"""威科夫分析入口:Cycle → Phase → Event → VP + LiveMULTI-CYCLE / LIVE-STRUCTURE)。
range.py 只产 TradingRangeConfirmed 走 events.pyLive 走 live.py。
cycles[0]=ACTIVE;禁止 cycles[-1] 取 active。
Execution 只消费 Confirmed(见 live.execution_signal_from_wyckoff)。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
import pandas as pd
from .events import build_phases, detect_bias_and_events
from .live import analyze_live_structure
from .range import detect_trading_ranges
from .volume_profile import compute_volume_profile
def _fmt_time(v) -> Optional[str]:
if v is None:
return None
if hasattr(v, "isoformat"):
try:
return v.isoformat()
except Exception:
pass
return str(v)
def _empty(vp_bins: int) -> Dict[str, Any]:
return {
"cycles": [],
"trading_range": None,
"bias": "unknown",
"phases": [],
"events": [],
"volume_profile": {"bins": [], "poc": None, "vah": None, "val": None, "bin_count": vp_bins},
"volume_confirm": {"avg_volume": 0.0, "event_checks": {}},
"live": None,
}
def _confidence_for_confirmed(
tr: Dict[str, Any],
phases: List[Dict[str, Any]],
events: List[Dict[str, Any]],
) -> Dict[str, float]:
range_c = float(tr.get("range_confidence") or 0.5)
labels = {p.get("phase") for p in phases}
phase_c = 0.35
if "A" in labels and "B" in labels:
phase_c += 0.15
if "C" in labels:
phase_c += 0.2
if "D" in labels or "E" in labels:
phase_c += 0.15
phase_c = min(0.95, phase_c)
types = {e.get("type") for e in events}
event_c = 0.25
for t in ("Spring", "UTAD", "SOS", "SOW", "LPS", "LPSY"):
if t in types:
event_c += 0.12
event_c = min(0.95, event_c)
overall = 0.4 * range_c + 0.3 * phase_c + 0.3 * event_c
return {
"range": round(range_c, 3),
"phase": round(phase_c, 3),
"event": round(event_c, 3),
"overall": round(overall, 3),
}
def _build_cycle(
work: pd.DataFrame,
tr: Dict[str, Any],
cycle_id: int,
vp_bins: int,
) -> Dict[str, Any]:
bias, events, volume_confirm = detect_bias_and_events(work, tr)
phases = build_phases(work, tr, bias, events)
vp = compute_volume_profile(
work,
int(tr["abs_start_idx"]),
int(tr["abs_end_idx"]),
bin_count=vp_bins,
)
for ev in events:
ev["time"] = _fmt_time(ev.get("time"))
for ph in phases:
ph["start_time"] = _fmt_time(ph.get("start_time"))
ph["end_time"] = _fmt_time(ph.get("end_time"))
is_active = cycle_id == 0
trading_range = {
"start_time": _fmt_time(tr.get("start_time")),
"end_time": _fmt_time(tr.get("end_time")),
"high": float(tr["high"]),
"low": float(tr["low"]),
"mid": float(tr["mid"]),
"active": bool(is_active),
"bars": int(tr.get("bars", 0)),
}
conf = _confidence_for_confirmed(tr, phases, events)
# Live 层:仅 ACTIVE 周期做推演;历史周期归档为 COMPLETED
if is_active:
live = analyze_live_structure(
work, tr, confirmed_events=events, confirmed_phases=phases, bias=bias,
)
lifecycle = live.get("lifecycle") or "FORMING"
else:
live = None
lifecycle = "COMPLETED"
return {
"id": int(cycle_id),
"role": "latest" if is_active else "historical",
# MULTI-CYCLE:时间线角色
"status": "ACTIVE" if is_active else "HISTORICAL",
# LIVE-STRUCTURE:生命周期
"lifecycle": lifecycle,
"direction": "latest" if is_active else "historical",
"period": {
"start_time": _fmt_time(tr.get("start_time")),
"end_time": _fmt_time(tr.get("end_time")),
"bars": int(tr.get("bars", 0)),
},
"confidence": conf,
"trading_range": trading_range,
"bias": bias,
# 兼容旧读法:顶层 phases/events = confirmed
"phases": phases,
"events": events,
"confirmed": {
"phases": phases,
"events": events,
"volume_confirm": volume_confirm,
},
"live": live,
"volume_profile": vp,
"volume_confirm": volume_confirm,
}
def analyze_wyckoff(
df: pd.DataFrame,
lookback: int = 120,
vp_bins: int = 50,
min_bars: int = 24,
atr_mult: float = 1.2,
range_start_time=None,
prefer_start_time=None,
max_cycles: int = 8,
) -> Dict[str, Any]:
"""
多周期威科夫分析。
cycles[0] = ACTIVE;顶层 phases/events 只镜像 Confirmed。
顶层 live 镜像 cycles[0].live。
"""
empty = _empty(vp_bins)
if df is None or len(df) < 30:
return empty
if not all(c in df.columns for c in ("open", "high", "low", "close")):
return empty
work = df.copy()
if "volume" not in work.columns:
work["volume"] = 1.0
trs = detect_trading_ranges(
work,
lookback=lookback,
min_bars=max(8, int(min_bars)),
atr_mult=atr_mult,
max_cycles=max(1, min(8, int(max_cycles))),
prefer_start_time=prefer_start_time,
range_start_time=range_start_time,
)
if not trs:
return empty
cycles: List[Dict[str, Any]] = []
for i, tr in enumerate(trs):
cycles.append(_build_cycle(work, tr, cycle_id=i, vp_bins=vp_bins))
active = cycles[0]
return {
"cycles": cycles,
"trading_range": active["trading_range"],
"bias": active["bias"],
"phases": active["confirmed"]["phases"],
"events": active["confirmed"]["events"],
"volume_profile": active["volume_profile"],
"volume_confirm": active["volume_confirm"],
"live": active.get("live"),
"lifecycle": active.get("lifecycle"),
}
+369
View File
@@ -0,0 +1,369 @@
"""威科夫阶段与事件(启发式)。"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
def _bar_time(df: pd.DataFrame, i: int):
row = df.iloc[i]
if "date" in df.columns and pd.notna(row["date"]):
return row["date"]
if "timestamp" in df.columns:
return row["timestamp"]
return i
def _avg_vol(df: pd.DataFrame, i: int, win: int = 20) -> float:
a = max(0, i - win + 1)
v = df["volume"].astype(float).iloc[a : i + 1]
m = float(v.mean()) if len(v) else 0.0
return m if m > 0 else 1.0
def detect_bias_and_events(
df: pd.DataFrame,
tr: Dict[str, Any],
) -> Tuple[str, List[Dict[str, Any]], Dict[str, Any]]:
"""
返回 bias、events、volume_confirm。
Spring/UTAD 相对「结构高低」判定:取区间内次低/次高(剔除单根极值),
避免箱体把假破低点吃进 lo 后永远刺不破、从而无 C 阶段。
"""
hi = float(tr["high"])
lo = float(tr["low"])
mid = float(tr["mid"])
tol = float(tr.get("tol") or (hi - lo) * 0.05)
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
events: List[Dict[str, Any]] = []
# 结构边界:用次低/次高作假破参照(至少 8 根才启用)
seg = df.iloc[s : e + 1]
event_lo, event_hi = lo, hi
if len(seg) >= 8:
lows = seg["low"].astype(float)
highs = seg["high"].astype(float)
# nsmallest(2) 的较大者 = 次低;nlargest(2) 的较小者 = 次高
event_lo = float(lows.nsmallest(min(2, len(lows))).iloc[-1])
event_hi = float(highs.nlargest(min(2, len(highs))).iloc[-1])
# 勿比公布箱沿更「松」:结构带应在箱内
event_lo = max(event_lo, lo)
event_hi = min(event_hi, hi)
# 若次低仍等于极值(多根同价),略抬参照便于识别收回
if abs(event_lo - lo) < 1e-12:
event_lo = lo + max(tol * 0.35, (hi - lo) * 0.02)
if abs(event_hi - hi) < 1e-12:
event_hi = hi - max(tol * 0.35, (hi - lo) * 0.02)
# 扫描区间内及之后(含 tail_reserve
scan_end = int(tr.get("abs_scan_end_idx", min(len(df) - 1, e + 15)))
scan_end = min(len(df) - 1, max(scan_end, e))
spring = None
utad = None
sos = None
sod = None # sign of weakness / distribution breakdown
lps = None
lpsy = None
for i in range(s + 2, scan_end + 1):
row = df.iloc[i]
low = float(row["low"])
high = float(row["high"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
# Spring: pierce below structural support then close back
if spring is None and low < event_lo - tol * 0.35 and close >= event_lo - tol * 0.35:
vol_ok = ratio <= 1.35 or (i + 1 <= scan_end and float(df.iloc[min(i + 1, scan_end)]["volume"]) / avg_v < 1.2)
spring = {
"type": "Spring",
"time": _bar_time(df, i),
"price": low,
"note": "假破下沿后收回",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# UTAD: pierce above structural resistance then close back
if utad is None and high > event_hi + tol * 0.35 and close <= event_hi + tol * 0.35:
vol_ok = ratio >= 0.8
utad = {
"type": "UTAD",
"time": _bar_time(df, i),
"price": high,
"note": "假破上沿后跌回",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# SOS: close above high with volume
if sos is None and close > hi + tol * 0.15:
vol_ok = ratio >= 1.15
sos = {
"type": "SOS",
"time": _bar_time(df, i),
"price": close,
"note": "放量上破交易区间",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# SOW / breakdown
if sod is None and close < lo - tol * 0.15:
vol_ok = ratio >= 1.15
sod = {
"type": "SOW",
"time": _bar_time(df, i),
"price": close,
"note": "放量下破交易区间",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
# LPS after SOS: pullback that holds above mid/high-band with lighter volume
if sos is not None:
si = int(sos["idx"])
for i in range(si + 1, min(len(df), si + 25)):
row = df.iloc[i]
low = float(row["low"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
if low >= mid - tol and close >= hi - tol * 2:
vol_ok = ratio <= 1.05
lps = {
"type": "LPS",
"time": _bar_time(df, i),
"price": low,
"note": "突破后缩量回踩不破",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
break
if sod is not None:
si = int(sod["idx"])
for i in range(si + 1, min(len(df), si + 25)):
row = df.iloc[i]
high = float(row["high"])
close = float(row["close"])
vol = float(row["volume"]) if "volume" in df.columns else 0.0
avg_v = _avg_vol(df, i)
ratio = vol / avg_v if avg_v else 0.0
if high <= mid + tol and close <= lo + tol * 2:
vol_ok = ratio <= 1.05
lpsy = {
"type": "LPSY",
"time": _bar_time(df, i),
"price": high,
"note": "下跌突破后缩量反抽不过",
"volume_ratio": round(ratio, 3),
"volume_ok": bool(vol_ok),
"idx": i,
}
break
# 冲突清理:已判定吸筹且有 SOS 时,丢弃更早的 UTAD(避免阶段/图面误导)
# 派发且有 SOW 时,丢弃更晚才合理的 Spring 假信号同理在偏置后再滤
keep = []
for ev in (spring, sos, lps, utad, sod, lpsy):
if not ev:
continue
keep.append(ev)
# bias(先算)
last_c = float(df["close"].iloc[-1])
bias = "unknown"
if sos and (not sod or int(sos.get("idx", 0)) >= int(sod.get("idx", 0))):
bias = "accumulation"
elif sod and (not sos or int(sod.get("idx", 0)) > int(sos.get("idx", 0))):
bias = "distribution"
elif spring and not utad:
bias = "accumulation"
elif utad and not spring:
bias = "distribution"
elif last_c >= mid:
bias = "accumulation"
else:
bias = "distribution"
filtered = []
for ev in keep:
if bias == "accumulation" and ev["type"] == "UTAD" and sos and int(ev["idx"]) <= int(sos["idx"]):
continue
if bias == "distribution" and ev["type"] == "Spring" and sod and int(ev["idx"]) <= int(sod["idx"]):
continue
filtered.append(ev)
events = [{k: v for k, v in ev.items() if k != "idx"} for ev in filtered]
avg_volume = float(df["volume"].astype(float).iloc[max(0, e - 20) : e + 1].mean()) if "volume" in df.columns else 0.0
volume_confirm = {
"avg_volume": avg_volume,
"event_checks": {ev["type"]: {"volume_ok": ev.get("volume_ok"), "volume_ratio": ev.get("volume_ratio")} for ev in events},
}
return bias, events, volume_confirm
def build_phases(
df: pd.DataFrame,
tr: Dict[str, Any],
bias: str,
events: List[Dict[str, Any]],
min_bars: int = 3,
) -> List[Dict[str, Any]]:
"""
按威科夫事件锚点切分 A–E(启发式)。
吸筹:A停止 → B筑底 → C测试(Spring) → D拉升(SOS…LPS) → E离开
派发:A停止 → B筑顶 → C测试(UTAD) → D派发(SOW…LPSY) → E离开
无 Spring/UTAD 时:若已有 SOS/SOW,用突破前末次沿带测试补 C;仍无则省略 C。
"""
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
hi = float(tr["high"])
lo = float(tr["low"])
n_last = len(df) - 1
min_span = max(2, min_bars - 1)
range_len = max(1, e - s)
def _match_idx(t) -> Optional[int]:
if t is None:
return None
lo = max(0, s - 2)
hi = min(len(df), e + 40)
for i in range(lo, hi):
if _bar_time(df, i) == t:
return i
try:
tt = pd.Timestamp(t)
sample = None
if "date" in df.columns and len(df):
sample = df["date"].iloc[min(s, n_last)]
if sample is not None and getattr(sample, "tzinfo", None) is not None and tt.tzinfo is None:
tt = tt.tz_localize(sample.tzinfo)
for i in range(lo, hi):
bt = _bar_time(df, i)
try:
if abs((pd.Timestamp(bt) - tt).total_seconds()) <= 1:
return i
except Exception:
continue
except Exception:
pass
return None
event_idx: Dict[str, int] = {}
for ev in events:
idx = _match_idx(ev.get("time"))
if idx is not None:
event_idx[str(ev.get("type"))] = idx
accum = bias != "distribution"
if accum:
c_ev = event_idx.get("Spring")
d_ev = event_idx.get("SOS")
d_tail = event_idx.get("LPS") or d_ev
else:
c_ev = event_idx.get("UTAD")
d_ev = event_idx.get("SOW")
d_tail = event_idx.get("LPSY") or d_ev
# 有 D 无明确测试事件时:用突破前最后一次触及下/上沿作为 C(次级测试)
if c_ev is None and d_ev is not None:
band = lo + (hi - lo) * 0.28 if accum else hi - (hi - lo) * 0.28
for i in range(int(d_ev) - 1, s + 1, -1):
row = df.iloc[i]
if accum and float(row["low"]) <= band:
c_ev = i
break
if not accum and float(row["high"]) >= band:
c_ev = i
break
def _lab(phase: str) -> str:
if accum:
m = {"A": "A停止下跌", "B": "B筑底", "C": "C测试", "D": "D拉升", "E": "E离开"}
else:
m = {"A": "A停止上涨", "B": "B筑顶", "C": "C测试", "D": "D派发", "E": "E离开"}
return m.get(phase, phase)
a_end = s + max(min_bars, range_len // 5)
c_start = c_end = None
if c_ev is not None:
c_start = max(s, int(c_ev) - 1)
c_end = min(n_last, int(c_ev) + 1)
if d_ev is not None:
d_start = int(d_ev)
d_end = min(n_last, max(int(d_tail or d_ev), d_start) + max(min_bars, range_len // 8))
if d_tail is not None:
d_end = max(d_end, min(n_last, int(d_tail) + 1))
else:
d_start = d_end = None
if c_start is not None:
b_end = max(a_end + 1, c_start)
elif d_start is not None:
b_end = max(a_end + 1, d_start)
else:
b_end = max(a_end + 1, e)
if d_end is not None:
e_start = min(n_last, d_end)
e_end = n_last
else:
e_start = e_end = None
raw = [("A", s, a_end), ("B", a_end, b_end)]
if c_start is not None and c_end is not None:
raw.append(("C", c_start, c_end))
if d_start is not None and d_end is not None:
raw.append(("D", d_start, d_end))
if e_start is not None and e_end is not None and e_end > e_start:
raw.append(("E", e_start, e_end))
phases: List[Dict[str, Any]] = []
cursor = s
for phase, _a, _b in raw:
if cursor >= n_last:
break
a = max(int(_a), cursor)
b = int(max(int(_b), a))
need = 1 if phase == "C" else min_span
if b < a + need:
b = min(n_last, a + need)
b = int(np.clip(b, a, n_last))
if b < a:
continue
if phases and phases[-1].get("_a") == a and phases[-1].get("_b") == b:
continue
phases.append(
{
"phase": phase,
"label": _lab(phase),
"start_time": _bar_time(df, a),
"end_time": _bar_time(df, b),
"_a": a,
"_b": b,
}
)
cursor = b
for p in phases:
p.pop("_a", None)
p.pop("_b", None)
return phases
+258
View File
@@ -0,0 +1,258 @@
"""威科夫 Live / Developing 层(WYCKOFF-LIVE-STRUCTURE-001)。
独立于 Confirmed Engine:不修改 events 确认条件,不写入 confirmed.events。
Execution 不得消费本模块输出。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Set
import numpy as np
import pandas as pd
def _avg_vol(df: pd.DataFrame, i: int, win: int = 20) -> float:
a = max(0, i - win + 1)
v = df["volume"].astype(float).iloc[a : i + 1]
m = float(v.mean()) if len(v) else 0.0
return m if m > 0 else 1.0
def _empty_live() -> Dict[str, Any]:
return {
"lifecycle": "UNKNOWN",
"range_formation": None,
"phase_candidate": None,
"event_candidates": [],
"next_expected": None,
"confidence": {
"cycle": 0.0,
"phase": 0.0,
"event": 0.0,
"structure": 0.0,
"volume": 0.0,
"overall": 0.0,
},
"note": "",
}
def analyze_live_structure(
df: pd.DataFrame,
tr: Optional[Dict[str, Any]],
confirmed_events: Optional[List[Dict[str, Any]]] = None,
confirmed_phases: Optional[List[Dict[str, Any]]] = None,
bias: str = "unknown",
) -> Dict[str, Any]:
"""
基于当前 TradingRange 与已确认事件,推演 Live candidates。
confirmed_* 只读,用于避免重复提示已确认事件,不修改之。
"""
out = _empty_live()
if df is None or len(df) < 20 or tr is None:
out["note"] = "insufficient structure"
return out
confirmed_events = confirmed_events or []
confirmed_phases = confirmed_phases or []
confirmed_types: Set[str] = {str(e.get("type")) for e in confirmed_events if e.get("type")}
s = int(tr["abs_start_idx"])
e = int(tr["abs_end_idx"])
scan_end = int(tr.get("abs_scan_end_idx", len(df) - 1))
scan_end = min(len(df) - 1, max(scan_end, e))
hi = float(tr["high"])
lo = float(tr["low"])
mid = float(tr["mid"])
tol = float(tr.get("tol") or (hi - lo) * 0.05)
atr = float(tr.get("atr") or max((hi - lo) * 0.2, 1e-9))
seg = df.iloc[s : e + 1]
if len(seg) < 8:
out["note"] = "range too short"
return out
# —— Range Formation(横盘 / 波动收敛)——
closes = seg["close"].astype(float)
highs = seg["high"].astype(float)
lows = seg["low"].astype(float)
vols = seg["volume"].astype(float) if "volume" in seg.columns else pd.Series([1.0] * len(seg))
half = max(4, len(seg) // 2)
vol_early = float(np.std(closes.iloc[:half])) if half > 1 else 0.0
vol_late = float(np.std(closes.iloc[-half:])) if half > 1 else 0.0
width = hi - lo
width_atr = width / atr if atr > 0 else 99.0
converging = vol_early > 1e-12 and vol_late < vol_early * 0.85
range_ok = 1.2 <= width_atr <= 10.0 and len(seg) >= 16
structure_score = 0.35
if range_ok:
structure_score += 0.25
if converging:
structure_score += 0.2
if width_atr <= 6.0:
structure_score += 0.1
structure_score = float(min(0.95, structure_score))
out["range_formation"] = {
"potential_trading_range": bool(range_ok),
"converging": bool(converging),
"width_atr": round(width_atr, 3),
"bars": int(len(seg)),
}
# —— 最近 K 形态(Phase C / Event candidates)——
i = scan_end
row = df.iloc[i]
o = float(row["open"])
h = float(row["high"])
l = float(row["low"])
c = float(row["close"])
rng = max(h - l, 1e-9)
lower_wick = min(o, c) - l
upper_wick = h - max(o, c)
avg_v = _avg_vol(df, i)
vol = float(row["volume"]) if "volume" in df.columns else avg_v
vol_ratio = vol / avg_v if avg_v else 1.0
volume_score = float(np.clip(1.1 - abs(vol_ratio - 1.0) * 0.35, 0.2, 0.95))
phase_candidate = None
phase_conf = 0.0
# Phase C:测低 + 下影 + 缩量(吸筹语境)
near_lo = l <= lo + tol * 1.2
test_low = l < mid and lower_wick >= rng * 0.35
vol_contract = vol_ratio <= 1.05
if bias != "distribution" and near_lo and test_low and vol_contract:
phase_candidate = "C"
phase_conf = 0.55 + (0.1 if lower_wick >= rng * 0.5 else 0) + (0.08 if vol_ratio < 0.9 else 0)
# Phase D 候选:价格在箱上半、有上破意图但未确认 SOS
elif c >= mid and (h >= hi - tol or c > hi - tol * 0.5):
phase_candidate = "D"
phase_conf = 0.5 + (0.1 if c > mid else 0)
elif c < mid and (l <= lo + tol):
phase_candidate = "B"
phase_conf = 0.45
# 已有 confirmed phase 时,candidate 取「下一阶段」提示,不覆盖事实
confirmed_phase_set = {str(p.get("phase")) for p in confirmed_phases}
if "E" in confirmed_phase_set:
phase_candidate = phase_candidate or "E"
phase_conf = max(phase_conf, 0.7)
elif "D" in confirmed_phase_set and phase_candidate is None:
phase_candidate = "D"
phase_conf = max(phase_conf, 0.65)
out["phase_candidate"] = phase_candidate
phase_conf = float(min(0.92, phase_conf))
# —— Event candidates(仅 Spring / SOS / LPS / UTAD)——
candidates: List[Dict[str, Any]] = []
def _add(typ: str, conf: float, note: str) -> None:
if typ in confirmed_types:
return # 已确认则不再作为 candidate
candidates.append(
{
"type": typ,
"confidence": round(float(min(0.9, conf)), 3),
"confirmed": False,
"note": note,
}
)
# Spring candidate:刺破或贴近下沿,收盘收回,但未达 Confirmed 规则(或不在 confirmed
pierce_lo = l < lo - tol * 0.15
close_back = c >= lo - tol * 0.5
if pierce_lo and close_back:
_add("Spring", 0.5 + (0.12 if vol_ratio <= 1.2 else 0) + (0.08 if close_back else 0), "假破下沿收回(未确认)")
elif l <= lo + tol * 0.35 and close_back and lower_wick >= rng * 0.4:
_add("Spring", 0.45 + (0.1 if vol_contract else 0), "测下沿长下影(未确认)")
# UTAD candidate
pierce_hi = h > hi + tol * 0.15
close_back_dn = c <= hi + tol * 0.5
if pierce_hi and close_back_dn:
_add("UTAD", 0.5 + (0.1 if vol_ratio >= 0.9 else 0), "假破上沿跌回(未确认)")
# SOS candidate:接近/轻破上沿,量能一般,未确认
if c > hi - tol * 0.4 or h >= hi:
sos_conf = 0.48 + (0.12 if c > hi else 0) + (0.1 if vol_ratio >= 1.05 else 0)
_add("SOS", sos_conf, "上破/逼近箱顶(未确认)")
# LPS candidate:站上 mid/上沿带后回踩
if c >= mid and l >= mid - tol * 1.5 and l > lo + (hi - lo) * 0.25:
_add("LPS", 0.46 + (0.1 if vol_ratio <= 1.0 else 0), "箱内上沿带回踩(未确认)")
candidates.sort(key=lambda x: x["confidence"], reverse=True)
out["event_candidates"] = candidates[:4]
event_score = float(candidates[0]["confidence"]) if candidates else 0.25
# next_expected(简规则)
next_exp = None
if "Spring" in confirmed_types and "SOS" not in confirmed_types:
next_exp = "SOS"
elif "SOS" in confirmed_types and "LPS" not in confirmed_types:
next_exp = "LPS"
elif "UTAD" in confirmed_types and "SOW" not in confirmed_types:
next_exp = "SOW"
elif any(c["type"] == "Spring" for c in candidates):
next_exp = "Test"
elif any(c["type"] == "SOS" for c in candidates):
next_exp = "LPS"
out["next_expected"] = next_exp
# —— lifecycle ——
key_confirmed = confirmed_types & {"Spring", "SOS", "UTAD", "SOW", "LPS", "LPSY"}
if key_confirmed:
lifecycle = "CONFIRMED"
elif range_ok or phase_candidate or candidates:
lifecycle = "FORMING"
else:
lifecycle = "UNKNOWN"
out["lifecycle"] = lifecycle
cycle_c = structure_score
overall = 0.35 * cycle_c + 0.25 * phase_conf + 0.25 * event_score + 0.15 * volume_score
out["confidence"] = {
"cycle": round(cycle_c, 3),
"phase": round(phase_conf, 3),
"event": round(event_score, 3),
"structure": round(structure_score, 3),
"volume": round(volume_score, 3),
"overall": round(float(overall), 3),
}
parts = []
if out["range_formation"]["potential_trading_range"]:
parts.append("Potential TR")
if phase_candidate:
parts.append(f"Phase {phase_candidate} candidate")
if candidates:
parts.append(f"{candidates[0]['type']} candidate")
out["note"] = "; ".join(parts) if parts else "observing"
return out
def execution_signal_from_wyckoff(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
Execution 边界:只允许 Confirmed。
返回 source='confirmed' 的信号描述;Live-only 时返回 None。
"""
if not payload:
return None
cycles = payload.get("cycles") or []
active = cycles[0] if cycles else None
events = []
if active and isinstance(active.get("confirmed"), dict):
events = list(active["confirmed"].get("events") or [])
if not events:
# 兼容旧顶层 events(均为 confirmed 镜像)
events = list(payload.get("events") or [])
if not events:
return None
last = events[-1]
return {
"source": "confirmed",
"type": last.get("type"),
"time": last.get("time"),
"lifecycle": (active or {}).get("lifecycle") or "CONFIRMED",
}
+442
View File
@@ -0,0 +1,442 @@
"""交易区间检测:仅负责 TradingRange(起止/高低/结构分)。
WYCKOFF-MULTI-CYCLE-001Phase/Event/VP 不得进入本模块。
过滤顺序固定:detect → quality → trend → overlap(<0.2) → accept → mask。
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
MAX_CYCLES = 8
OVERLAP_RATIO_MAX = 0.2
def _atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
high = df["high"].astype(float)
low = df["low"].astype(float)
close = df["close"].astype(float)
prev_close = close.shift(1)
tr = pd.concat(
[
(high - low).abs(),
(high - prev_close).abs(),
(low - prev_close).abs(),
],
axis=1,
).max(axis=1)
return tr.rolling(period, min_periods=max(3, period // 2)).mean()
def _robust_width(seg: pd.DataFrame) -> float:
"""用 90/10 分位估宽,避免单根影线把长窗卡死。"""
h = seg["high"].astype(float)
l = seg["low"].astype(float)
if len(seg) < 6:
return float(h.max() - l.min())
return float(np.nanpercentile(h, 90) - np.nanpercentile(l, 10))
def _score_segment(
length: int,
near_hi: int,
near_lo: int,
inside: float,
width: float,
atr: float,
) -> float:
"""结构质量分(非 Phase/Event)。"""
touch = min(near_hi, 6) + min(near_lo, 6)
width_pen = (width / atr) if atr > 0 else width
return float(touch) * 4.0 + float(inside) * 25.0 - width_pen * 3.0 + min(length / 40.0, 2.0)
def _time_col(df: pd.DataFrame) -> Optional[str]:
if "date" in df.columns:
return "date"
if "timestamp" in df.columns:
return "timestamp"
return None
def _bar_index_at_or_after(work: pd.DataFrame, ts: Any) -> Optional[int]:
col = _time_col(work)
if col is None or ts is None:
return None
try:
target = pd.Timestamp(ts)
except Exception:
return None
series = pd.to_datetime(work[col], utc=True, errors="coerce")
if target.tzinfo is None:
target = target.tz_localize("UTC")
else:
target = target.tz_convert("UTC")
if series.isna().all():
return None
ge = series >= target
if ge.any():
return int(np.flatnonzero(ge.to_numpy())[0])
return 0
def _pack_range(
work: pd.DataFrame,
df: pd.DataFrame,
start_i: int,
end_i: int,
hi: float,
lo: float,
tol: float,
last_atr: float,
score: float,
n: int,
window_offset: int = 0,
) -> Dict[str, Any]:
"""组装 TradingRange(仅结构字段)。"""
mid = (hi + lo) / 2.0
last_c = float(work["close"].iloc[min(end_i, len(work) - 1)])
price_in_box = (lo - tol * 1.5) <= last_c <= (hi + tol * 1.5)
bars = int(end_i - start_i + 1)
# 结构置信:归一化 score(启发式)
range_conf = float(np.clip(score / 55.0, 0.05, 0.99))
best = {
"start_idx": int(start_i),
"end_idx": int(end_i),
"high": float(hi),
"low": float(lo),
"mid": float(mid),
"active": bool(price_in_box),
"atr": float(last_atr),
"tol": float(tol),
"bars": bars,
"score": float(score),
"quality": float(score),
"range_confidence": range_conf,
}
def _ts(row) -> Any:
col = _time_col(work)
if col and pd.notna(row[col]):
return row[col]
return None
best["start_time"] = _ts(work.iloc[best["start_idx"]])
best["end_time"] = _ts(work.iloc[best["end_idx"]])
# window_offsetslice 相对父 DataFrame 的起点;勿用 len(df)-len(work)
offset = int(window_offset)
best["abs_start_idx"] = offset + best["start_idx"]
best["abs_end_idx"] = offset + best["end_idx"]
best["abs_scan_end_idx"] = offset + n - 1
return best
def _overlap_ratio(a0: int, a1: int, b0: int, b1: int) -> float:
"""两闭区间重叠长度 / 较短区间长度。"""
lo = max(a0, b0)
hi = min(a1, b1)
if hi < lo:
return 0.0
overlap = hi - lo + 1
shorter = min(a1 - a0 + 1, b1 - b0 + 1)
if shorter <= 0:
return 0.0
return float(overlap) / float(shorter)
def _passes_quality(tr: Dict[str, Any], min_bars: int) -> bool:
if tr is None:
return False
if int(tr.get("bars") or 0) < max(8, min_bars // 2):
return False
if float(tr.get("score") or 0) < 12.0:
return False
hi = float(tr["high"])
lo = float(tr["low"])
atr = float(tr.get("atr") or 0) or 1.0
if (hi - lo) / atr > 12.0:
return False
return True
def _passes_trend_filter(work: pd.DataFrame, tr: Dict[str, Any]) -> bool:
"""趋势污染:定向位移过大则非震荡箱。"""
s = int(tr["start_idx"])
e = int(tr["end_idx"])
seg = work.iloc[s : e + 1]
if len(seg) < 8:
return False
c0 = float(seg["close"].iloc[0])
c1 = float(seg["close"].iloc[-1])
atr = float(tr.get("atr") or 0) or 1.0
drift = abs(c1 - c0) / atr
# 相对箱宽:漂移占箱宽过大 → 趋势
width = max(float(tr["high"]) - float(tr["low"]), atr)
drift_frac = abs(c1 - c0) / width
if drift > 6.0 and drift_frac > 0.55:
return False
return True
def _detect_in_window(
df: pd.DataFrame,
win_start: int,
win_end: int,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
prefer_start_time: Any = None,
range_start_time: Any = None,
) -> Optional[Dict[str, Any]]:
"""
在 df[win_start:win_end+1] 内检测单个 TradingRange。
只返回箱体结构,不含 Phase/Event/VP。
"""
if df is None or win_end < win_start:
return None
slice_df = df.iloc[win_start : win_end + 1].reset_index(drop=True)
lookback = len(slice_df)
if lookback < min_bars + 5:
return None
work = slice_df
n = len(work)
reserve = min(tail_reserve, max(0, n - min_bars - 2))
core_end = n - reserve if reserve > 0 else n
core = work.iloc[:core_end]
if len(core) < min_bars:
core = work
core_end = n
reserve = 0
atr = _atr(work)
last_atr = float(atr.iloc[core_end - 1]) if atr.notna().iloc[:core_end].any() else float(
(core["high"] - core["low"]).mean()
)
if not np.isfinite(last_atr) or last_atr <= 0:
last_atr = float(core["close"].iloc[-1]) * 0.01
eff_atr_mult = float(atr_mult)
if lookback >= 280:
eff_atr_mult = atr_mult * 1.7
elif lookback >= 160:
eff_atr_mult = atr_mult * 1.3
width_factor = 3.8 + min(2.2, max(0.0, (lookback - 80) / 100.0))
max_width = last_atr * eff_atr_mult * width_factor
tol = last_atr * eff_atr_mult * 0.35
prefer_i = None
if prefer_start_time is not None:
prefer_i = _bar_index_at_or_after(work, prefer_start_time)
if range_start_time is not None:
start_i = _bar_index_at_or_after(work, range_start_time)
if start_i is not None and start_i <= core_end - 8:
seg = work.iloc[start_i:core_end]
hi = float(seg["high"].max())
lo = float(seg["low"].min())
rw = _robust_width(seg)
if 0 < rw <= max_width * 1.15:
near_hi = int((seg["high"] >= hi - tol).sum())
near_lo = int((seg["low"] <= lo + tol).sum())
inside = float(((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean())
if near_hi >= 2 and near_lo >= 2 and inside >= 0.70:
score = _score_segment(len(seg), near_hi, near_lo, inside, rw, last_atr)
return _pack_range(
work, df, start_i, core_end - 1, hi, lo, tol, last_atr, score, n,
window_offset=win_start,
)
eff_min_bars = max(8, int(min_bars))
cn = len(core)
max_bars = min(cn, max(eff_min_bars * 2, min(96, max(eff_min_bars + 8, int(cn * 0.5)))))
cands: List[Tuple[float, int, int, int, float, float, float]] = []
def _try_seg(start_i: int, end_i: int, prefer_boost: float = 0.0) -> None:
if end_i - start_i + 1 < eff_min_bars:
return
if start_i < 0 or end_i >= cn or start_i > end_i:
return
seg = work.iloc[start_i : end_i + 1]
hi = float(seg["high"].max())
lo = float(seg["low"].min())
rw = _robust_width(seg)
if rw <= 0 or rw > max_width:
return
raw_w = hi - lo
if raw_w > max_width * 1.35:
return
near_hi = int((seg["high"] >= hi - tol).sum())
near_lo = int((seg["low"] <= lo + tol).sum())
if near_hi < 2 or near_lo < 2:
return
inside = float(((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean())
if inside < 0.72:
return
length = end_i - start_i + 1
score = _score_segment(length, near_hi, near_lo, inside, rw, last_atr) + prefer_boost
cands.append((score, length, start_i, end_i, hi, lo, rw))
for length in range(min(cn, max_bars), eff_min_bars - 1, -4):
start_i = cn - length
boost = 0.0
if prefer_i is not None:
dist = abs(start_i - int(prefer_i))
if dist <= 6:
boost = 10.0
elif dist <= 14:
boost = 4.0
elif start_i > int(prefer_i) + 16:
boost = -10.0
_try_seg(start_i, cn - 1, boost)
if prefer_i is not None:
pi = int(prefer_i)
if 0 <= pi < cn:
align_max = min(cn, max(max_bars, int(cn * 0.65)))
alen = cn - pi
if eff_min_bars <= alen <= align_max:
_try_seg(pi, cn - 1, prefer_boost=18.0)
elif alen > align_max:
start_i = max(0, cn - align_max)
if start_i > pi:
start_i = pi
end_i = min(cn - 1, pi + align_max - 1)
else:
end_i = cn - 1
_try_seg(start_i, end_i, prefer_boost=12.0)
if not cands:
return None
cands.sort(key=lambda x: x[0], reverse=True)
best_score = cands[0][0]
band = max(4.0, abs(best_score) * 0.10)
near = [c for c in cands if c[0] >= best_score - band]
chosen = max(near, key=lambda x: (x[1], x[0]))
score, _length, start_i, end_i, hi, lo, _rw = chosen
return _pack_range(work, df, start_i, end_i, hi, lo, tol, last_atr, score, n, window_offset=win_start)
def detect_trading_ranges(
df: pd.DataFrame,
lookback: Optional[int] = None,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
max_cycles: int = MAX_CYCLES,
prefer_start_time: Any = None,
range_start_time: Any = None,
) -> List[Dict[str, Any]]:
"""
倒序切多段 TradingRange(近→远)。
过滤顺序:detect → quality → trend → overlap → accept → mask。
返回列表已按时间倒序,调用方将 [0] 标为 ACTIVE。
"""
if df is None or len(df) < min_bars + 5:
return []
lb = int(lookback) if lookback is not None else len(df)
work = df.tail(lb).reset_index(drop=True)
n = len(work)
occupied: List[Dict[str, Any]] = []
accepted: List[Dict[str, Any]] = []
# 搜索右端从 n-1 往左收缩;每接受一段后右端移到该段 start 之前
search_end = n - 1
prefer = prefer_start_time
hard_start = range_start_time
while len(accepted) < max(1, int(max_cycles)) and search_end >= min_bars + 4:
# 在剩余历史内从右往左试多个右边界,避免历史箱必须贴住 search_end
# (否则中间趋势会挡住更早的真实箱)
cand = None
step = max(4, min(12, (search_end - min_bars) // 10 or 4))
for end_try in range(search_end, min_bars + 4, -step):
trial = _detect_in_window(
work,
0,
end_try,
min_bars=min_bars,
atr_mult=atr_mult,
tail_reserve=tail_reserve,
prefer_start_time=prefer if len(accepted) == 0 and end_try == search_end else None,
range_start_time=hard_start if len(accepted) == 0 and end_try == search_end else None,
)
# 1) detect
if trial is None:
continue
# 2) quality
if not _passes_quality(trial, min_bars):
continue
# 3) trend contamination
if not _passes_trend_filter(work, trial):
continue
# 4) overlap with accepted
a0, a1 = int(trial["abs_start_idx"]), int(trial["abs_end_idx"])
overlap_bad = False
for occ in occupied:
ratio = _overlap_ratio(a0, a1, int(occ["start"]), int(occ["end"]))
if ratio >= OVERLAP_RATIO_MAX:
overlap_bad = True
break
if overlap_bad:
continue
# 取最靠右的合格箱(倒序第一段)
cand = trial
break
if cand is None:
break
# 5) accept
accepted.append(cand)
a0, a1 = int(cand["abs_start_idx"]), int(cand["abs_end_idx"])
# 6) mask
occupied.append(
{
"start": a0,
"end": max(a1, int(cand.get("abs_scan_end_idx", a1))),
"quality": float(cand.get("quality") or 0),
"high": float(cand["high"]),
"low": float(cand["low"]),
}
)
# 下一轮只在更早窗口搜
search_end = int(cand["abs_start_idx"]) - 1
hard_start = None
prefer = None
# abs_* 目前相对 work;若 df 比 work 长需加 offset
offset = len(df) - len(work)
if offset:
for tr in accepted:
tr["abs_start_idx"] = int(tr["abs_start_idx"]) + offset
tr["abs_end_idx"] = int(tr["abs_end_idx"]) + offset
tr["abs_scan_end_idx"] = int(tr["abs_scan_end_idx"]) + offset
return accepted
def detect_trading_range(
df: pd.DataFrame,
lookback: int = 120,
min_bars: int = 24,
atr_mult: float = 1.2,
tail_reserve: int = 12,
range_start_time: Any = None,
prefer_start_time: Any = None,
) -> Optional[Dict[str, Any]]:
"""兼容旧接口:返回倒序列表中的第一段(ACTIVE 候选)。"""
ranges = detect_trading_ranges(
df,
lookback=lookback,
min_bars=min_bars,
atr_mult=atr_mult,
tail_reserve=tail_reserve,
max_cycles=1,
prefer_start_time=prefer_start_time,
range_start_time=range_start_time,
)
return ranges[0] if ranges else None
@@ -0,0 +1,72 @@
"""区间内 Volume Profile。"""
from __future__ import annotations
from typing import Any, Dict, List
import numpy as np
import pandas as pd
def compute_volume_profile(
df: pd.DataFrame,
start_idx: int,
end_idx: int,
bin_count: int = 50,
value_area_pct: float = 0.70,
) -> Dict[str, Any]:
seg = df.iloc[start_idx : end_idx + 1]
if seg.empty:
return {"bins": [], "poc": None, "vah": None, "val": None, "bin_count": bin_count}
typical = (seg["high"].astype(float) + seg["low"].astype(float) + seg["close"].astype(float)) / 3.0
vol = seg["volume"].astype(float).fillna(0.0)
lo = float(seg["low"].min())
hi = float(seg["high"].max())
if not np.isfinite(lo) or not np.isfinite(hi) or hi <= lo:
mid = float(seg["close"].iloc[-1])
return {
"bins": [{"price": mid, "volume": float(vol.sum())}],
"poc": mid,
"vah": mid,
"val": mid,
"bin_count": 1,
}
edges = np.linspace(lo, hi, bin_count + 1)
# 右开最后一桶闭合
idx = np.clip(np.digitize(typical.values, edges) - 1, 0, bin_count - 1)
vols = np.zeros(bin_count, dtype=float)
for i, v in zip(idx, vol.values):
vols[i] += float(v)
centers = (edges[:-1] + edges[1:]) / 2.0
poc_i = int(np.argmax(vols)) if vols.sum() > 0 else bin_count // 2
poc = float(centers[poc_i])
# Value Area:从 POC 向两侧扩展直到累计 >= value_area_pct
total = float(vols.sum()) or 1.0
target = total * value_area_pct
left = right = poc_i
acc = float(vols[poc_i])
while acc < target and (left > 0 or right < bin_count - 1):
left_v = vols[left - 1] if left > 0 else -1.0
right_v = vols[right + 1] if right < bin_count - 1 else -1.0
if right_v >= left_v and right < bin_count - 1:
right += 1
acc += float(vols[right])
elif left > 0:
left -= 1
acc += float(vols[left])
else:
break
bins: List[Dict[str, float]] = [
{"price": float(centers[i]), "volume": float(vols[i])} for i in range(bin_count)
]
return {
"bins": bins,
"poc": poc,
"vah": float(centers[right]),
"val": float(centers[left]),
"bin_count": bin_count,
}
-4
View File
@@ -281,10 +281,6 @@ 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):
-40
View File
@@ -1,40 +0,0 @@
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}>"
-196
View File
@@ -1,196 +0,0 @@
"""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,
)
+4 -2
View File
@@ -6,7 +6,9 @@ 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
@@ -465,7 +467,7 @@ class BiBuilderMixin:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False:
#pre_last_bi.update_bi(klc)
pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
#last_top.set_fx(Chan_FX_TYPE.PTOP)
@@ -583,7 +585,7 @@ class BiBuilderMixin:
pre_last_bi = bi_list[-2]
last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False:
#pre_last_bi.update_bi(klc)
pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
last_bottom = klc
+2
View File
@@ -6,7 +6,9 @@ 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
-148
View File
@@ -1,148 +0,0 @@
"""第四类买卖点(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_bsp3scan / 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
+1 -1
View File
@@ -9,7 +9,7 @@ from datetime import datetime
import pandas as pd
from pandas import DataFrame
from chanlun.pipeline.resample import resample_to_interval
from technical.util import resample_to_interval
from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_KLC_STATE
from chanlun.core.ChanKLU import ChanKLU
+4 -3
View File
@@ -6,8 +6,9 @@ from decimal import Decimal
import numpy as np
import pandas as pd
from chanlun.indicators import ta
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
@@ -54,8 +55,8 @@ class IndicatorsBuilderMixin:
return None
def add_indicators(self, df):
fast = 26
slow = 52
fast = 12
slow = 26
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
+2 -15
View File
@@ -6,7 +6,9 @@ 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
@@ -98,21 +100,6 @@ 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:
+2
View File
@@ -6,7 +6,9 @@ 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
+2
View File
@@ -6,7 +6,9 @@ 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
+2 -2
View File
@@ -17,7 +17,9 @@ 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
@@ -169,8 +171,6 @@ 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):
-52
View File
@@ -1,52 +0,0 @@
"""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
+3 -4
View File
@@ -2,8 +2,9 @@ from datetime import timedelta
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from chanlun.pipeline.resample import resample_to_interval
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
@@ -30,14 +31,13 @@ from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
from chanlun.pipeline.builders.bi import BiBuilderMixin
from chanlun.pipeline.builders.bsp import BspBuilderMixin
from chanlun.pipeline.builders.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, FastBspBuilderMixin, IncrementalBuilderMixin):
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin, IncrementalBuilderMixin):
def __init__(self, df=None, interval=0, timeframe=None):
if df is not None:
self.init_TF_DF(df, interval, timeframe)
@@ -62,7 +62,6 @@ class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilde
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)
-198
View File
@@ -1,198 +0,0 @@
"""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()
+84
View File
@@ -0,0 +1,84 @@
{
"$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
}
}
+98
View File
@@ -0,0 +1,98 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"strategy": "BTC_Maker_Micro_Scalper",
"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_maker_micro_scalper.sqlite",
"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": true,
"fee": 0.00016,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 3,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8821,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "change_me_mms_v1",
"ws_token": "change_me_mms_ws",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "BTC_Maker_Micro_Scalper",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 1
}
}
+98
View File
@@ -0,0 +1,98 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"strategy": "BTC_Maker_Micro_Scalper_v11",
"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_maker_micro_scalper_v11.sqlite",
"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": true,
"fee": 0.00016,
"unfilledtimeout": {
"entry": 3,
"exit": 2,
"exit_timeout_count": 3,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "change_me_mms_v11",
"ws_token": "change_me_mms_v11_ws",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "BTC_Maker_Micro_Scalper_v11",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 1
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_perpetual.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "YOUR_BINANCE_API_KEY",
"secret": "YOUR_BINANCE_API_SECRET",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "YOUR_TELEGRAM_BOT_TOKEN",
"chat_id": "YOUR_TELEGRAM_CHAT_ID"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "change_me_to_a_random_secret_key",
"ws_token": "change_me_to_a_random_ws_token",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "BTC_Perpetual_Bot",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$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
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"$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": {
"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": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+89
View File
@@ -0,0 +1,89 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_1m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {
"proxies": {
"http": "http://127.0.0.1:7897",
"https": "http://127.0.0.1:7897"
}
},
"ccxt_async_config": {
"aiohttp_proxy": "http://127.0.0.1:7897"
},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "8197349375:AAH208JghCq8raFYF-IpnobYknCr6iGDH_0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 1
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+68
View File
@@ -0,0 +1,68 @@
{
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_5m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 5,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {
"proxies": {
"http": "http://127.0.0.1:7897",
"https": "http://127.0.0.1:7897"
}
},
"ccxt_async_config": {
"aiohttp_proxy": "http://127.0.0.1:7897"
},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"internals": {
"process_throttle_secs": 5
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+87
View File
@@ -0,0 +1,87 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_k.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_eth_60.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ETH/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": false,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": false,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8813,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+151
View File
@@ -0,0 +1,151 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.95,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol_optimized.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": false,
"unfilledtimeout": {
"entry": 2,
"exit": 2,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {
"options": {"defaultType": "swap"}
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000,
"timeout": 30000
},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": []
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "error",
"jwt_secret_key": "",
"username": "",
"password": ""
},
"discord": {
"enabled": false,
"webhook": "",
"webhook_avatar": "",
"poll_delay_seconds": 10
},
"notification_settings": {
"status": "on",
"status_inactive_after": 7,
"timeframe_condition_change": "on",
"telegram": { },
"discord": { },
"notify_all": true
},
"bot_name": "SOL_Chan_Optimized",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.007,
"stoploss_range_step": 0.001,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"order_types": {
"entry": "limit",
"exit": "market",
"emergency_exit": "market",
"force_exit": "market",
"force_entry": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"strategy_path": "./user_data/Chan/strategies/",
"strategy": "ChanLun_SOL_Optimized",
"minimal_roi": {
"0": 0.012,
"120": 0.010,
"240": 0.007,
"360": 0.005
},
"stoploss": -0.007,
"trailing_stop": true,
"trailing_stop_positive": 0.003,
"trailing_stop_positive_offset": 0.005,
"trailing_only_offset_is_reached": true,
"use_custom_stoploss": true,
"max_open_trades_per_pair": 1,
"dry_run_wallet_refresh_time": 5,
"caches": {
"dataframe": {
"enabled": true,
"refresh_period": 60
},
"strategy": {
"enabled": true,
"refresh_period": 300
}
},
"pairlists": [
{
"method": "StaticPairList",
"config": {
"pairs": ["SOL/USDT:USDT"]
}
}
]
}
+125
View File
@@ -0,0 +1,125 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chanlun_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"freqai": {
"enabled": true,
"purge_old_models": true,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier": "chanLun1",
"live_retrain_hours": 1,
"expiration_hours": 48,
"fit_live_predictions_candles": 0,
"data_kitchen_thread_count": 4,
"save_backtest_models": true,
"save_metadata": true,
"feature_parameters": {
"include_timeframes": [
"1m",
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20, 30],
"allow_duplicate_train": true
},
"data_split_parameters": {
"test_size": 0.25
},
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"max_depth": 5,
"subsample": 0.8,
"colsample_bytree": 0.8,
"use_label_for_weight": true,
"booster": "gbtree",
"num_class": 2
}
},
"freqaimodel": "XGBoostClassifier",
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8811,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+103
View File
@@ -0,0 +1,103 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "30m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "ocQUqAPSD9PDhIL2lTMlMan0wMFwvvu5Fv8eYF3wUM8yPytm2jBgz51cgiHXw7J6",
"secret": "yHIc6FOnSoOI2FvygpRKKku4FKaZGI5DSwC83Ip4wRfUcxszennF6hy2vhbVuLYJ",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
"SUI/USDT:USDT",
"1INCH/USDT:USDT",
"DOGE/USDT:USDT",
"TON/USDT:USDT",
"UNI/USDT:USDT",
"XRP/USDT:USDT",
"SUN/USDT:USDT",
"NOT/USDT:USDT",
"RARE/USDT:USDT",
"RDNT/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "VolumePairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "5m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8088,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"strategy": "ChanStrategy",
"db_url": "sqlite:///tradesv3.btc_chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDC",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "hyperliquid",
"walletAddress": "0xA834b6d3Fa1D8A55ea8e502685ef5cbD2b2D3343",
"privateKey": "0xa399cea4c01be67c16b88e1d2121ed6e72bab6b4e8d81684ed03ce78f8b4f827",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"PURR/USDC:USDC",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8815,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan_sol_30.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8818,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.chan.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800,
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8801,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+93
View File
@@ -0,0 +1,93 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"db_url": "sqlite:///tradesv3.deepseek_trader.sqlite",
"dry_run": true,
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT",
"ETH/USDT:USDT",
"SOL/USDT:USDT",
"WIF/USDT:USDT",
"1000PEPE/USDT:USDT",
"DOGS/USDT:USDT",
"ORDI/USDT:USDT",
"AAVE/USDT:USDT",
"REEF/USDT:USDT",
"1000SATS/USDT:USDT",
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 10,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8001,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": true,
"internals": {
"process_throttle_secs": 15
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema26_ema52_cross.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8820,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.ema_pattern.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1h",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8888,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+70
View File
@@ -0,0 +1,70 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.elliottwave_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 3,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "freqtrade_secret",
"ws_token": "freqtrade_ws",
"username": "freqtrade",
"password": "freqtrade"
},
"bot_name": "ElliottWaveBTC"
}
+123
View File
@@ -0,0 +1,123 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.freqai_sol.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "5m",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList"
}
],
"freqai": {
"enabled": true,
"purge_old_models": 2,
"train_period_days": 10,
"backtest_period_days": 7,
"live_retrain_hours": 1,
"identifier": "sol_futures_lgbm_v1",
"feature_parameters": {
"include_timeframes": [
"5m",
"15m"
],
"include_corr_pairlist": [
"BTC/USDT:USDT"
],
"label_period_candles": 12,
"include_shifted_candles": 1,
"DI_threshold": 0.9,
"weight_factor": 0.9,
"principal_component_analysis": false,
"use_SVM_to_remove_outliers": true,
"indicator_periods_candles": [
14
],
"plot_feature_importances": 0
},
"data_split_parameters": {
"test_size": 0.15,
"random_state": 42
},
"model_training_parameters": {
"n_estimators": 300,
"learning_rate": 0.05,
"max_depth": 5,
"num_leaves": 31,
"min_child_samples": 20,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 0.1,
"n_jobs": 1,
"verbosity": -1
}
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqai_sol",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.heikinashi_btc.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "1m",
"process_only_new_candles" : false,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": true,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8814,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$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
}
}
+91
View File
@@ -0,0 +1,91 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"strategy": "MakerEdgeProbe",
"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.maker_edge_probe.sqlite",
"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,
"fee": 0.00016,
"unfilledtimeout": {
"entry": 3,
"exit": 2,
"exit_timeout_count": 3,
"unit": "minutes"
},
"order_types": {
"entry": "limit",
"exit": "limit",
"stoploss": "market",
"stoploss_on_exchange": false
},
"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 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": {
"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"
}
],
"telegram": {
"enabled": false
},
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8823,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "maker_edge_probe_change_me",
"ws_token": "maker_edge_probe_ws",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "MakerEdgeProbe",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 2
}
}
+81
View File
@@ -0,0 +1,81 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.sol5m.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"unfilledtimeout": {
"entry": 1,
"exit": 1,
"exit_timeout_count": 5,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"SOL/USDT:USDT"
],
"pair_blacklist": [
"BNB/.*"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 1800
}
],
"telegram": {
"enabled": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "SOL5m",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"$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.
+86
View File
@@ -0,0 +1,86 @@
{
"$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.turtle_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "15m",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 15,
"exit": 15,
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8822,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "turtle-btc-change-me",
"ws_token": "turtle-btc-ws-change-me",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "turtle_btc",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+86
View File
@@ -0,0 +1,86 @@
{
"$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.wyckoff_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1h",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 60,
"exit": 60,
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8823,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "wyckoff-btc-change-me",
"ws_token": "wyckoff-btc-ws-change-me",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "wyckoff_btc",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+86
View File
@@ -0,0 +1,86 @@
{
"$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.wyckoff_btc_gated.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1h",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 60,
"exit": 60,
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8825,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "wyckoff-gated-change-me",
"ws_token": "wyckoff-gated-ws-change-me",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "wyckoff_btc_gated",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+86
View File
@@ -0,0 +1,86 @@
{
"$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.wyckoff_btc_lps.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1h",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 60,
"exit": 60,
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8824,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "wyckoff-lps-change-me",
"ws_token": "wyckoff-lps-ws-change-me",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "wyckoff_btc_lps",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+86
View File
@@ -0,0 +1,86 @@
{
"$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.wyckoff_btc_v1_baseline.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short": true,
"timeframe": "1h",
"process_only_new_candles": true,
"unfilledtimeout": {
"entry": 60,
"exit": 60,
"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": {
"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"
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8823,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "wyckoff-v1-baseline-change-me",
"ws_token": "wyckoff-v1-baseline-ws-change-me",
"CORS_origins": [],
"username": "freqtrader",
"password": "FreqTrade007"
},
"bot_name": "wyckoff_btc_v1_baseline",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
+5
View File
@@ -0,0 +1,5 @@
"""crypto_wyckoff — multi-TF screener for crypto (ported from A_Share_DP Architecture v1.0)."""
from crypto_wyckoff.version import ARCHITECTURE_VERSION, WYCKOFF_ENGINE_VERSION
__all__ = ["WYCKOFF_ENGINE_VERSION", "ARCHITECTURE_VERSION"]
+342
View File
@@ -0,0 +1,342 @@
"""Walk-forward Wyckoff phase/event annotations for chart overlay."""
from __future__ import annotations
from datetime import date
from crypto_wyckoff.domain_models import OHLCVFrame, WyckoffCycle, WyckoffEvent, WyckoffPhase
from crypto_wyckoff.cycle import CycleEngine
from crypto_wyckoff.event import EventEngine
from crypto_wyckoff.features import FeatureEngine
from crypto_wyckoff.phase import PhaseEngine
_MIN_BARS = {"1d": 40, "1w": 26, "1M": 18}
_NOTABLE_EVENTS = {
WyckoffEvent.PS.value,
WyckoffEvent.SC.value,
WyckoffEvent.AR.value,
WyckoffEvent.ST.value,
WyckoffEvent.SPRING.value,
WyckoffEvent.TEST.value,
WyckoffEvent.SOS.value,
WyckoffEvent.LPS.value,
WyckoffEvent.JUMP.value,
WyckoffEvent.BACKUP.value,
WyckoffEvent.BC.value,
WyckoffEvent.UTAD.value,
WyckoffEvent.SOW.value,
WyckoffEvent.LPSY.value,
}
def _slice_frame(frame: OHLCVFrame, end_idx: int) -> OHLCVFrame:
n = end_idx + 1
return OHLCVFrame(
ts_code=frame.ts_code,
timeframe=frame.timeframe,
trade_dates=frame.trade_dates[:n],
open=frame.open[:n],
high=frame.high[:n],
low=frame.low[:n],
close=frame.close[:n],
volume=frame.volume[:n],
amount=frame.amount[:n] if frame.amount else [],
)
def _compress_phases(points: list[tuple[str, str]]) -> list[dict]:
"""points: [(date_iso, phase), ...] → segments."""
if not points:
return []
segs: list[dict] = []
start, phase = points[0]
prev = start
for d, p in points[1:]:
if p != phase:
segs.append({"start": start, "end": prev, "phase": phase})
start, phase = d, p
prev = d
segs.append({"start": start, "end": prev, "phase": phase})
return segs
def annotate_frame(
frame: OHLCVFrame,
step: int | None = None,
*,
role: str | None = None,
) -> dict:
"""Pure annotation: phase bands + event markers + latest levels.
``role`` is the D/W/M rule alias (1d/1w/1M). Defaults to frame.timeframe.
``step`` defaults by role to keep interactive charts snappy.
"""
tf = role or frame.timeframe
min_bars = _MIN_BARS.get(tf, 30)
if step is None:
step = {"1d": 2, "1w": 1, "1M": 1}.get(tf, 2)
empty = {
"phases": [],
"events": [],
"levels": {},
"bars": len(frame),
"timeframe": tf,
}
if frame.empty or len(frame) < min_bars:
return empty
feat_eng = FeatureEngine()
cycle_eng = CycleEngine()
phase_eng = PhaseEngine()
event_eng = EventEngine()
phase_points: list[tuple[str, str]] = []
events: list[dict] = []
last_event: str | None = None
levels: dict = {}
# Ensure last bar is always evaluated
indices = list(range(min_bars - 1, len(frame), step))
if indices[-1] != len(frame) - 1:
indices.append(len(frame) - 1)
for i in indices:
sub = _slice_frame(frame, i)
f = feat_eng.run(sub, tf)
c = cycle_eng.run(f, tf)
p = phase_eng.run(c, f, tf)
e = event_eng.run(c, p, f, tf)
d = str(frame.trade_dates[i])[:10]
phase = p.payload.get("phase") or WyckoffPhase.NONE.value
phase_points.append((d, phase))
cur = e.payload.get("current_event") or WyckoffEvent.NONE.value
if cur in _NOTABLE_EVENTS and cur != last_event:
events.append({
"date": d,
"event": cur,
"price": float(frame.close[i]),
"low": float(frame.low[i]),
"high": float(frame.high[i]),
})
last_event = cur
elif cur == WyckoffEvent.NONE.value:
last_event = None
if i == len(frame) - 1 and not f.payload.get("insufficient"):
levels = {
k: f.payload.get(k)
for k in (
"range_high", "range_low", "ma20", "ma60",
"swing_high", "swing_low", "close",
)
if f.payload.get(k) is not None
}
levels["phase"] = phase
levels["cycle"] = c.payload.get("cycle")
levels["current_event"] = cur
return {
"phases": _compress_phases(phase_points),
"events": events,
"levels": levels,
"bars": len(frame),
"timeframe": tf,
}
_RANGE_CYCLES = {
WyckoffCycle.ACCUMULATION.value,
WyckoffCycle.RE_ACCUMULATION.value,
WyckoffCycle.DISTRIBUTION.value,
WyckoffCycle.RE_DISTRIBUTION.value,
}
def _build_range_zones(
price_frame: OHLCVFrame,
cycle_segs: list[dict],
levels: dict | None = None,
) -> list[dict]:
"""Build price boxes (high/low × date span) for accum/distrib ranges."""
if price_frame.empty:
return []
dates = [str(d)[:10] for d in price_frame.trade_dates]
highs = price_frame.high
lows = price_frame.low
zones: list[dict] = []
for seg in cycle_segs or []:
cy = seg.get("cycle")
if cy not in _RANGE_CYCLES:
continue
start, end = seg["start"], seg["end"]
idxs = [i for i, d in enumerate(dates) if start <= d <= end]
if not idxs:
# weekly bar date may sit between daily bars — take nearest window
i0 = next((i for i, d in enumerate(dates) if d >= start), None)
if i0 is None:
continue
i1 = next((i for i, d in enumerate(dates) if d > end), len(dates)) - 1
idxs = list(range(i0, max(i0, i1) + 1))
if not idxs:
continue
# pad short weekly hits to at least ~1 week of dailies for visibility
if len(idxs) < 5 and idxs[-1] + 1 < len(dates):
extra = min(5 - len(idxs), len(dates) - 1 - idxs[-1])
idxs = list(range(idxs[0], idxs[-1] + 1 + max(0, extra)))
hi = max(highs[i] for i in idxs)
lo = min(lows[i] for i in idxs)
if hi <= lo:
continue
zones.append({
"kind": cy,
"start": dates[idxs[0]],
"end": dates[idxs[-1]],
"high": float(hi),
"low": float(lo),
"current": False,
})
# Always expose the latest trading-range box from feature snapshot
levels = levels or {}
rh, rl = levels.get("range_high"), levels.get("range_low")
if rh is not None and rl is not None and float(rh) > float(rl):
look = min(60, len(dates))
cy = levels.get("cycle") or "Unknown"
if cy not in _RANGE_CYCLES:
# Phase B/C in a range → treat as accumulation-style TR for display
ph = levels.get("phase") or ""
if ph in ("A", "B", "C"):
cy = WyckoffCycle.ACCUMULATION.value
elif ph in ("D", "E") and float(levels.get("close") or 0) < float(rh):
cy = WyckoffCycle.ACCUMULATION.value
else:
cy = "Range"
zones.append({
"kind": cy,
"start": dates[-look],
"end": dates[-1],
"high": float(rh),
"low": float(rl),
"current": True,
})
return zones
def annotate_symbol(
ts_code: str,
freq: str,
end_date: date | None = None,
lookback: int = 180,
*,
combo_id: str | None = None,
) -> dict:
"""IO + annotate for one symbol (used by API).
For the combo *low* chart, phase bands come from **mid** structure,
while event markers / levels come from the low TF.
"""
from crypto_wyckoff.combos import ROLE_HIGH, ROLE_LOW, ROLE_MID, get_combo
from crypto_wyckoff.io import load_frame
combo = get_combo(combo_id)
allowed = {combo["low"], combo["mid"], combo["high"]}
if freq not in allowed:
raise ValueError(f"freq {freq} not in combo {combo['id']} ({combo['label']})")
empty = {
"ts_code": ts_code,
"freq": freq,
"phases": [],
"events": [],
"levels": {},
"zones": [],
"bars": 0,
"phase_source": freq,
"cycles": [],
"combo_id": combo["id"],
}
_ = end_date
if freq == combo["low"]:
low = load_frame(ts_code, combo["low"], lookback)
mid = load_frame(ts_code, combo["mid"], max(60, lookback // 3))
if low is None:
return empty
d_ann = annotate_frame(low, role=ROLE_LOW)
w_ann = annotate_frame(mid, role=ROLE_MID) if mid is not None else {"phases": []}
cycles = _cycle_segments(mid, role=ROLE_MID) if mid is not None else []
levels = d_ann.get("levels") or {}
if cycles:
levels = {**levels, "cycle": cycles[-1].get("cycle") or levels.get("cycle")}
for p in reversed(w_ann.get("phases") or []):
if p.get("phase") not in (None, "None"):
levels = {**levels, "phase": p["phase"]}
break
return {
"ts_code": ts_code,
"freq": freq,
"end_date": low.trade_dates[-1].isoformat() if low.trade_dates else None,
"phases": w_ann.get("phases") or [],
"events": d_ann.get("events") or [],
"levels": d_ann.get("levels") or {},
"zones": _build_range_zones(low, cycles, levels),
"bars": d_ann.get("bars", 0),
"phase_source": combo["mid"],
"cycles": cycles,
"combo_id": combo["id"],
}
role = ROLE_MID if freq == combo["mid"] else ROLE_HIGH
frame = load_frame(ts_code, freq, lookback)
if frame is None:
return empty
out = annotate_frame(frame, role=role)
out["ts_code"] = ts_code
out["freq"] = freq
out["end_date"] = frame.trade_dates[-1].isoformat() if frame.trade_dates else None
out["phase_source"] = freq
out["cycles"] = _cycle_segments(frame, role=ROLE_HIGH if role == ROLE_HIGH else ROLE_MID)
out["zones"] = _build_range_zones(frame, out["cycles"], out.get("levels") or {})
out["combo_id"] = combo["id"]
if role == ROLE_HIGH:
if not any(p.get("phase") not in (None, "None") for p in out["phases"]):
out["phases"] = [
{"start": c["start"], "end": c["end"], "phase": c["cycle"]}
for c in out["cycles"]
if c.get("cycle") and c["cycle"] != "Unknown"
]
return out
def _cycle_segments(
frame: OHLCVFrame,
step: int | None = None,
*,
role: str | None = None,
) -> list[dict]:
"""Walk-forward cycle labels compressed to segments."""
tf = role or frame.timeframe
min_bars = _MIN_BARS.get(tf, 30)
if step is None:
step = {"1d": 3, "1w": 1, "1M": 1}.get(tf, 2)
if frame.empty or len(frame) < min_bars:
return []
feat_eng = FeatureEngine()
cycle_eng = CycleEngine()
points: list[tuple[str, str]] = []
indices = list(range(min_bars - 1, len(frame), step))
if indices[-1] != len(frame) - 1:
indices.append(len(frame) - 1)
for i in indices:
sub = _slice_frame(frame, i)
f = feat_eng.run(sub, tf)
c = cycle_eng.run(f, tf)
points.append((str(frame.trade_dates[i])[:10], c.payload.get("cycle") or "Unknown"))
segs = _compress_phases(points)
return [{"start": s["start"], "end": s["end"], "cycle": s["phase"]} for s in segs]
+248
View File
@@ -0,0 +1,248 @@
"""Multi-timeframe combo presets for Crypto Wyckoff Screener.
Roles (engine rule aliases stay D/W/M):
high Cycle (rules as 1M)
mid Phase (rules as 1w)
low Event (rules as 1d)
Actual bar TFs come from the combo (e.g. 8h/4h/1h).
"""
from __future__ import annotations
import json
import re
import threading
from copy import deepcopy
from pathlib import Path
from typing import Any
from crypto_wyckoff.io import DATA_DIR, ensure_dirs
ROLE_LOW = "1d"
ROLE_MID = "1w"
ROLE_HIGH = "1M"
# Minutes for ordering / validation (provider labels)
_TF_MINUTES: dict[str, int] = {
"1m": 1, "2m": 2, "3m": 3, "4m": 4, "5m": 5,
"10m": 10, "15m": 15, "20m": 20, "25m": 25, "30m": 30, "45m": 45,
"1h": 60, "2h": 120, "3h": 180, "4h": 240, "5h": 300,
"6h": 360, "7h": 420, "8h": 480, "9h": 540, "10h": 600,
"11h": 660, "12h": 720, "16h": 960, "20h": 1200,
"1d": 1440, "2d": 2880, "3d": 4320, "4d": 5760, "5d": 7200, "6d": 8640,
"1w": 10080, "2w": 20160, "3w": 30240,
"1M": 43200,
}
# TFs we allow in custom combos (provider-backed + local 1M)
ALLOWED_TFS: tuple[str, ...] = (
"1h", "2h", "3h", "4h", "6h", "8h", "12h",
"1d", "2d", "3d", "1w", "1M",
)
BUILTIN: list[dict[str, Any]] = [
{
"id": "h8_4_1",
"label": "8h / 4h / 1h",
"high": "8h",
"mid": "4h",
"low": "1h",
"builtin": True,
},
{
"id": "d_w_m",
"label": "1d / 1w / 1M",
"high": "1M",
"mid": "1w",
"low": "1d",
"builtin": True,
},
]
_COMBOS_FILE = DATA_DIR / "combos.json"
_lock = threading.Lock()
_cache: list[dict[str, Any]] | None = None
def tf_minutes(tf: str) -> int | None:
if tf in _TF_MINUTES:
return _TF_MINUTES[tf]
# tolerate provider typo "10" → skip
m = re.fullmatch(r"(\d+)([mhdwM])", tf)
if not m:
return None
n, u = int(m.group(1)), m.group(2)
mult = {"m": 1, "h": 60, "d": 1440, "w": 10080, "M": 43200}[u]
return n * mult
def combo_id_for(high: str, mid: str, low: str) -> str:
def _tok(t: str) -> str:
return t.replace("/", "_")
return f"{_tok(high)}_{_tok(mid)}_{_tok(low)}"
def validate_combo(high: str, mid: str, low: str) -> str | None:
"""Return error message or None if ok."""
for tf in (high, mid, low):
if tf not in ALLOWED_TFS:
return f"不支持的周期: {tf}"
if len({high, mid, low}) < 3:
return "高/中/低周期必须互不相同"
hm, mm, lm = tf_minutes(high), tf_minutes(mid), tf_minutes(low)
if hm is None or mm is None or lm is None:
return "无法解析周期长度"
if not (hm > mm > lm):
return "须满足 高 > 中 > 低(例如 8h > 4h > 1h"
return None
def _normalize(row: dict[str, Any]) -> dict[str, Any] | None:
high, mid, low = row.get("high"), row.get("mid"), row.get("low")
if not high or not mid or not low:
return None
err = validate_combo(str(high), str(mid), str(low))
if err:
return None
cid = str(row.get("id") or combo_id_for(high, mid, low))
label = str(row.get("label") or f"{high} / {mid} / {low}")
return {
"id": cid,
"label": label,
"high": str(high),
"mid": str(mid),
"low": str(low),
"builtin": bool(row.get("builtin", False)),
}
def _load_raw() -> list[dict[str, Any]]:
ensure_dirs()
if not _COMBOS_FILE.exists():
return deepcopy(BUILTIN)
try:
data = json.loads(_COMBOS_FILE.read_text(encoding="utf-8"))
items = data.get("combos") if isinstance(data, dict) else data
if not isinstance(items, list):
return deepcopy(BUILTIN)
except (OSError, json.JSONDecodeError):
return deepcopy(BUILTIN)
out: list[dict[str, Any]] = []
seen: set[str] = set()
for b in BUILTIN:
out.append(deepcopy(b))
seen.add(b["id"])
for row in items:
if not isinstance(row, dict):
continue
norm = _normalize(row)
if not norm or norm["id"] in seen:
continue
if norm["id"] in {b["id"] for b in BUILTIN}:
continue
norm["builtin"] = False
out.append(norm)
seen.add(norm["id"])
return out
def _save(combos: list[dict[str, Any]]) -> None:
ensure_dirs()
custom = [c for c in combos if not c.get("builtin")]
payload = {"combos": custom}
tmp = _COMBOS_FILE.with_suffix(".tmp")
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
tmp.replace(_COMBOS_FILE)
def list_combos() -> list[dict[str, Any]]:
global _cache
with _lock:
if _cache is None:
_cache = _load_raw()
return deepcopy(_cache)
def get_combo(combo_id: str | None) -> dict[str, Any]:
combos = list_combos()
if combo_id:
for c in combos:
if c["id"] == combo_id:
return deepcopy(c)
return deepcopy(combos[0])
def add_combo(high: str, mid: str, low: str, label: str | None = None) -> dict[str, Any]:
err = validate_combo(high, mid, low)
if err:
raise ValueError(err)
cid = combo_id_for(high, mid, low)
row = {
"id": cid,
"label": label or f"{high} / {mid} / {low}",
"high": high,
"mid": mid,
"low": low,
"builtin": False,
}
with _lock:
combos = _load_raw()
for c in combos:
if c["id"] == cid or (c["high"], c["mid"], c["low"]) == (high, mid, low):
_cache = combos
return deepcopy(c)
combos.append(row)
_save(combos)
_cache = combos
return deepcopy(row)
def delete_combo(combo_id: str) -> bool:
with _lock:
combos = _load_raw()
kept: list[dict[str, Any]] = []
removed = False
for c in combos:
if c["id"] == combo_id:
if c.get("builtin"):
raise ValueError("内置组合不可删除")
removed = True
continue
kept.append(c)
if removed:
_save(kept)
_cache = kept
return removed
def all_tfs_for_combos(combos: list[dict[str, Any]] | None = None) -> list[str]:
"""Unique TFs needed by active combos (stable order)."""
rows = combos if combos is not None else list_combos()
seen: list[str] = []
for c in rows:
for k in ("low", "mid", "high"):
tf = c[k]
if tf not in seen:
seen.append(tf)
return seen
def lookback_for(tf: str) -> int:
defaults = {
"1h": 500,
"2h": 400,
"3h": 350,
"4h": 300,
"6h": 280,
"8h": 250,
"12h": 220,
"1d": 250,
"2d": 200,
"3d": 180,
"1w": 104,
"1M": 60,
}
return defaults.get(tf, 200)
+102
View File
@@ -0,0 +1,102 @@
"""Cycle Engine — monthly/weekly macro cycle via Rule Registry."""
from __future__ import annotations
from crypto_wyckoff.domain_models import EngineResult, WyckoffCycle
from crypto_wyckoff.rules.base import RuleHit
from crypto_wyckoff.rules.registry import rule_registry
def _resolve_range_conflict(hits: list[RuleHit], features: dict) -> list[RuleHit]:
"""Accumulation vs Distribution overlap → mutually exclusive by MA120 position."""
accum = [h for h in hits if h.cycle == WyckoffCycle.ACCUMULATION.value]
dist = [h for h in hits if h.cycle == WyckoffCycle.DISTRIBUTION.value]
if not (accum and dist):
return hits
close = float(features.get("close") or 0)
ma120 = float(features.get("ma120") or close) or close
others = [
h for h in hits
if h.cycle not in (WyckoffCycle.ACCUMULATION.value, WyckoffCycle.DISTRIBUTION.value)
]
# Below MA120 → accumulation; above → distribution; equal band uses relative position
if close < ma120 * 0.995:
return others + accum
if close > ma120 * 1.005:
return others + dist
# Tight band: keep higher confidence only
best_a = max(accum, key=lambda h: h.confidence)
best_d = max(dist, key=lambda h: h.confidence)
return others + ([best_a] if best_a.confidence >= best_d.confidence else [best_d])
class CycleEngine:
name = "Cycle"
version = "1.0.0"
def run(self, feature: EngineResult, timeframe: str) -> EngineResult:
features = feature.payload
if features.get("insufficient"):
return EngineResult(
name=self.name,
version=self.version,
confidence=15.0,
score=40.0,
reasons=[f"{timeframe} 数据不足,Cycle=Unknown"],
warnings=["insufficient_features"],
payload={
"cycle": WyckoffCycle.UNKNOWN.value,
"timeframe": timeframe,
"trend_score": 40.0,
},
)
context = {"features": features, "timeframe": timeframe}
hits: list[RuleHit] = []
for rule in rule_registry.by_category("cycle", timeframe):
hit = rule.evaluate(context)
if hit and hit.cycle:
hits.append(hit)
hits = _resolve_range_conflict(hits, features)
if not hits:
return EngineResult(
name=self.name,
version=self.version,
confidence=30.0,
score=40.0,
reasons=["无匹配周期规则,标记 Unknown"],
payload={
"cycle": WyckoffCycle.UNKNOWN.value,
"timeframe": timeframe,
"trend_score": 40.0,
},
)
best = max(hits, key=lambda h: h.confidence)
trend_score = best.score
if best.cycle == WyckoffCycle.MARKUP.value:
trend_score = max(trend_score, 75.0)
elif best.cycle == WyckoffCycle.ACCUMULATION.value:
trend_score = max(60.0, trend_score * 0.9)
elif best.cycle == WyckoffCycle.DISTRIBUTION.value:
trend_score = min(45.0, 100 - trend_score * 0.5)
elif best.cycle == WyckoffCycle.MARKDOWN.value:
trend_score = min(30.0, 100 - trend_score)
return EngineResult(
name=self.name,
version=self.version,
confidence=best.confidence,
score=trend_score,
reasons=best.reasons,
metrics=best.metrics,
payload={
"cycle": best.cycle,
"timeframe": timeframe,
"rule_id": best.rule_id,
"trend_score": trend_score,
},
)

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