添加新的策略
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
# 1分钟第三类买卖点策略
|
||||
|
||||
## 核心思路
|
||||
|
||||
只交易 1 分钟级别中枢之后确认完成的第三类买卖点。
|
||||
|
||||
- 第三类买点:价格向上离开 1 分钟中枢后,回拉笔低点不跌回中枢上沿,确认时做多。
|
||||
- 第三类卖点:价格向下离开 1 分钟中枢后,反弹笔高点不涨回中枢下沿,确认时做空。
|
||||
- 开单时机:第三类买卖点所在笔确认完成后,下一根 1 分钟 K 线开单,避免使用未确认信号。
|
||||
|
||||
## 初始量化参数
|
||||
|
||||
以下参数作为第一版回测基准,后续根据回测结果优化。
|
||||
|
||||
| 参数 | 初始值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 基础周期 | 1m | 第三类买卖点识别周期 |
|
||||
| 中枢算法 | 纯笔中枢 | 连续三笔重叠形成中枢,两个中枢允许相邻,不强制中间分割笔 |
|
||||
| 大周期过滤 | 5m、15m | 用于判断趋势方向和过滤震荡 |
|
||||
| ATR 周期 | 14 | 用于衡量离开力度、回抽深度和止损距离 |
|
||||
| 成交量均线 | 20 | 用于判断离开放量和回抽缩量 |
|
||||
| 最小中枢宽度 | 0.08% | 低于该值视为噪音中枢 |
|
||||
| 最大中枢宽度 | 0.80% | 高于该值止损过宽,放弃交易 |
|
||||
| 有效突破距离 | max(0.03%, 0.20 * ATR14 / close) | 离开中枢时收盘价需要超过边界的最小距离 |
|
||||
| 离开笔最小幅度 | max(0.12%, 1.00 * ATR14 / close) | 过滤力度不足的离开笔 |
|
||||
| 回抽最大距离 | 0.60 * ATR14 | 回抽/反弹离中枢边界太远时,不追单 |
|
||||
| 离开放量 | volume >= 1.20 * volume_ma20 | 确认突破有主动资金 |
|
||||
| 回抽缩量 | pullback_volume <= 0.90 * leave_volume | 确认回抽不是反向强攻击 |
|
||||
| 最大止损距离 | 0.80% | 超过则放弃交易 |
|
||||
| 最小止损距离 | 0.10% | 低于则容易被 1m 噪音扫损 |
|
||||
| 单笔风险 | 0.5% - 1.0% | 每笔亏损控制在账户权益比例内 |
|
||||
| 时间止损 | 8 根 1m K 线 | 开仓后 8 分钟仍未到 0.5R,主动减仓或平仓 |
|
||||
| 连续失败暂停 | 2 次 | 连续 2 次三买/三卖失败后暂停 30 分钟 |
|
||||
|
||||
## 信号有效条件
|
||||
|
||||
### 中枢要求
|
||||
|
||||
- 中枢必须已经确认,不能用正在形成中的中枢。
|
||||
- 使用 1 分钟纯笔中枢:连续三笔有重叠区间即可形成中枢,后续按两笔一组延伸。
|
||||
- 两个中枢可以在笔序列上直接相邻,不要求中间必须有独立分割笔。
|
||||
- 新中枢在旧中枢下方时,必须以向上笔开始并以向上笔结束,避免把下跌途中的弱反抽误当成有效下移中枢。
|
||||
- 新中枢在旧中枢上方时,必须以向下笔开始并以向下笔结束,避免把上涨途中的弱回踩误当成有效上移中枢。
|
||||
- 中枢宽度控制在 0.08% - 0.80% 之间,太小容易是假突破,太大导致止损距离过宽。
|
||||
- 优先选择结构清晰、震荡时间充分、上下沿明确的中枢。
|
||||
- 中枢层只做结构合法性判断,不因为成交量、离开力度、回抽质量等交易偏好直接删除中枢;这些质量条件放到买卖点确认和入场过滤中处理。
|
||||
|
||||
### 离开中枢要求
|
||||
|
||||
- 做多时,离开笔必须向上有效突破中枢上沿。
|
||||
- 做空时,离开笔必须向下有效跌破中枢下沿。
|
||||
- 有效突破要求收盘价至少超过中枢边界 max(0.03%, 0.20 * ATR14 / close)。
|
||||
- 离开笔幅度至少达到 max(0.12%, 1.00 * ATR14 / close)。
|
||||
- 离开笔成交量至少达到 1.20 * volume_ma20。
|
||||
- MACD 柱子方向需要和离开方向一致,做多时 macdhist > 0,做空时 macdhist < 0。
|
||||
- 如果离开中枢后很快又回到中枢内部,视为假突破,不开单。
|
||||
|
||||
### 回抽/反弹要求
|
||||
|
||||
- 做多时,回抽低点不能跌回中枢上沿下方。
|
||||
- 做空时,反弹高点不能涨回中枢下沿上方。
|
||||
- 回抽/反弹允许 0.15 * ATR14 的刺破容忍,避免被 1m 假刺破过滤掉。
|
||||
- 回抽/反弹距离中枢边界不能超过 0.60 * ATR14,超过说明已经追远。
|
||||
- 回抽/反弹成交量需要小于离开笔成交量的 90%。
|
||||
- 回抽/反弹 K 线数量建议控制在 2 - 8 根 1m K 线内,太短容易没确认,太长说明力度衰减。
|
||||
|
||||
## 行情过滤
|
||||
|
||||
### 震荡行情
|
||||
|
||||
震荡行情尽量不做第三类买卖点,因为 1 分钟级别假突破很多。
|
||||
|
||||
过滤方式:
|
||||
|
||||
- 1 分钟只负责寻找第三类买卖点,5 分钟优先负责判断是否接受该信号。
|
||||
- 5 分钟和 15 分钟方向不一致时不做。
|
||||
- 5 分钟最近中枢仍在横向扩张、价格仍在 5 分钟中枢内部时,降低 1 分钟三买/三卖信号优先级,或直接不做突破类信号。
|
||||
- 做多信号优先要求 5 分钟中枢上移或价格位于 5 分钟中枢上沿附近/上方;做空信号优先要求 5 分钟中枢下移或价格位于 5 分钟中枢下沿附近/下方。
|
||||
- 价格反复穿越 EMA24/EMA52 时不做。
|
||||
- 中枢上下沿附近频繁出现假突破时不做。
|
||||
- 最近 30 分钟内出现 2 次同方向三买/三卖失败时,暂停该方向交易 30 分钟。
|
||||
- 最近 20 根 1m K 线内,收盘价穿越 EMA52 超过 4 次,视为震荡,不做。
|
||||
- ATR14 / close 低于 0.05% 时,波动不足,不做。
|
||||
|
||||
### 趋势开始阶段
|
||||
|
||||
趋势刚开始时的第一个有效三买/三卖优先级最高。
|
||||
|
||||
做多条件:
|
||||
|
||||
- 5 分钟或 15 分钟开始转多,至少满足 close > EMA52。
|
||||
- 1 分钟向上离开中枢有力度。
|
||||
- 回抽不跌回中枢,且回抽缩量。
|
||||
|
||||
做空条件:
|
||||
|
||||
- 5 分钟或 15 分钟开始转空,至少满足 close < EMA52。
|
||||
- 1 分钟向下离开中枢有力度。
|
||||
- 反弹不涨回中枢,且反弹缩量。
|
||||
|
||||
### 趋势中期
|
||||
|
||||
趋势中期可以继续做顺势三买/三卖,但需要提高过滤要求。
|
||||
|
||||
- 只做顺大周期方向的信号。
|
||||
- 做多时 5 分钟 close > EMA24 > EMA52,且 15 分钟 close > EMA52。
|
||||
- 做空时 5 分钟 close < EMA24 < EMA52,且 15 分钟 close < EMA52。
|
||||
- 如果止损距离超过 0.80%,放弃交易。
|
||||
- 趋势中期的同方向第二个及之后三买/三卖,仓位降为标准仓位的 50%。
|
||||
|
||||
### 趋势末期
|
||||
|
||||
趋势末期减少追单,重点防止三买买在高点、三卖卖在低点。
|
||||
|
||||
不交易条件:
|
||||
|
||||
- 离开中枢时 MACD 或成交量明显背驰。
|
||||
- 已经连续出现多个同方向中枢上移/下移。
|
||||
- 出现反向第一类或第二类买卖点。
|
||||
- 价格远离 5 分钟 EMA52 超过 max(1.20%, 2.50 * ATR14 / close),短线加速过度。
|
||||
- 连续 3 个同方向中枢上移/下移后,不再追新的 1m 三买/三卖。
|
||||
|
||||
## 特殊点位处理
|
||||
|
||||
### 第一类和第二类买卖点之后
|
||||
|
||||
如果出现第一类或第二类买卖点后,行情没有继续确认反转,而是重新形成第三类买卖点:
|
||||
|
||||
- 顺原趋势的第三类买卖点可以继续做,但必须确认反向一二类买卖点失败。
|
||||
- 如果一类/二类买卖点之后形成更大级别反转结构,不再做原方向三买/三卖。
|
||||
- 如果一类/二类买卖点和三类买卖点方向冲突,以大周期方向和最新确认结构为准。
|
||||
|
||||
### 反向信号
|
||||
|
||||
- 持有多单时出现确认的第三类卖点,平多;如果大周期也转空,可以反手做空。
|
||||
- 持有空单时出现确认的第三类买点,平空;如果大周期也转多,可以反手做多。
|
||||
|
||||
## 开仓规则
|
||||
|
||||
### 做多
|
||||
|
||||
同时满足以下条件才开多:
|
||||
|
||||
- 出现确认后的 1 分钟第三类买点。
|
||||
- 5 分钟或 15 分钟趋势不为空头。
|
||||
- 价格没有重新跌回中枢内部。
|
||||
- 初始止损距离在可接受范围内。
|
||||
- 没有明显背驰或趋势末期信号。
|
||||
- 开仓价距离中枢上沿不超过 0.60 * ATR14。
|
||||
- 止损距离在 0.10% - 0.80% 之间。
|
||||
|
||||
### 做空
|
||||
|
||||
同时满足以下条件才开空:
|
||||
|
||||
- 出现确认后的 1 分钟第三类卖点。
|
||||
- 5 分钟或 15 分钟趋势不为多头。
|
||||
- 价格没有重新涨回中枢内部。
|
||||
- 初始止损距离在可接受范围内。
|
||||
- 没有明显背驰或趋势末期信号。
|
||||
- 开仓价距离中枢下沿不超过 0.60 * ATR14。
|
||||
- 止损距离在 0.10% - 0.80% 之间。
|
||||
|
||||
## 信号失效
|
||||
|
||||
- 第三类买点确认后,价格重新跌回中枢上沿下方,信号失效。
|
||||
- 第三类卖点确认后,价格重新涨回中枢下沿上方,信号失效。
|
||||
- 开仓后 8 根 1 分钟 K 线仍未达到 0.5R,说明信号弱,可以主动减仓或平仓。
|
||||
- 开仓后 3 根 1 分钟 K 线内直接回到中枢内部,立即平仓。
|
||||
- 出现反向确认信号时,当前持仓失效。
|
||||
|
||||
## 止盈止损
|
||||
|
||||
### 止损
|
||||
|
||||
- 做多止损:放在中枢下沿,或第三类买点回抽低点下方。
|
||||
- 做空止损:放在中枢上沿,或第三类卖点反弹高点上方。
|
||||
- 止损需要额外留出 0.10 * ATR14 的缓冲,避免刚好打在结构边界。
|
||||
- 如果止损距离大于 0.80%,不开仓。
|
||||
- 如果止损距离小于 0.10%,按 0.10% 计算仓位风险,避免仓位过大。
|
||||
- 如果价格重新回到中枢内部,优先考虑提前止损,不等硬止损。
|
||||
|
||||
### 止盈
|
||||
|
||||
按照风险收益比管理:
|
||||
|
||||
- 到达 1R 时平仓一半。
|
||||
- 到达 1R 后,剩余仓位止损移动到开仓价。
|
||||
- 到达 2R 时全部止盈。
|
||||
- 如果趋势特别强,可以在 2R 附近保留小仓位,用 EMA24 或前一笔低/高点跟踪止盈。
|
||||
|
||||
### 仓位
|
||||
|
||||
- 标准单笔风险控制在账户权益的 0.5% - 1.0%。
|
||||
- 趋势开始阶段使用标准仓位。
|
||||
- 趋势中期第二个及之后同方向三买/三卖使用 50% 标准仓位。
|
||||
- 趋势末期不主动开新仓。
|
||||
|
||||
## 参数优化方法
|
||||
|
||||
这些参数不能只看单次回测收益率,需要用历史数据做分阶段优化和样本外验证。
|
||||
|
||||
### 数据切分
|
||||
|
||||
建议至少使用 6 - 12 个月 1m 数据,按时间顺序切分,不能随机打乱。
|
||||
|
||||
- 训练集:前 60%,用于搜索参数。
|
||||
- 验证集:中间 20%,用于选择参数。
|
||||
- 测试集:最后 20%,只用于最终确认,不参与调参。
|
||||
|
||||
例如:
|
||||
|
||||
- 2025-01 到 2025-06:训练集。
|
||||
- 2025-07 到 2025-08:验证集。
|
||||
- 2025-09 到 2025-10:测试集。
|
||||
|
||||
如果数据足够多,建议再做滚动验证:
|
||||
|
||||
- 第 1 轮:1 - 3 月训练,4 月验证。
|
||||
- 第 2 轮:2 - 4 月训练,5 月验证。
|
||||
- 第 3 轮:3 - 5 月训练,6 月验证。
|
||||
- 只有多轮都稳定的参数,才认为有效。
|
||||
|
||||
### 优先优化的参数
|
||||
|
||||
不要一次优化太多参数,先优化最影响胜率和盈亏比的核心参数。
|
||||
|
||||
| 参数 | 搜索范围 | 步长 | 优化目的 |
|
||||
| --- | --- | --- | --- |
|
||||
| 最小中枢宽度 | 0.05% - 0.15% | 0.02% | 过滤噪音中枢 |
|
||||
| 最大中枢宽度 | 0.50% - 1.20% | 0.10% | 控制止损距离 |
|
||||
| 有效突破距离 | 0.10 - 0.40 * ATR14 | 0.05 | 过滤假突破 |
|
||||
| 离开笔最小幅度 | 0.80 - 1.50 * ATR14 | 0.10 | 确认离开力度 |
|
||||
| 回抽容忍幅度 | 0.05 - 0.25 * ATR14 | 0.05 | 避免过严或过松 |
|
||||
| 回抽最大距离 | 0.40 - 0.90 * ATR14 | 0.10 | 避免追高追低 |
|
||||
| 离开放量倍数 | 1.00 - 1.80 * volume_ma20 | 0.10 | 确认突破质量 |
|
||||
| 回抽缩量比例 | 0.70 - 1.00 * leave_volume | 0.05 | 判断回抽是否健康 |
|
||||
| 最大止损距离 | 0.50% - 1.20% | 0.10% | 控制单笔风险 |
|
||||
| 时间止损 K 线数 | 5 - 15 根 | 1 | 处理无效信号 |
|
||||
|
||||
第一轮只优化这些参数。大周期过滤、仓位、止盈方式先固定,否则容易过拟合。
|
||||
|
||||
### 优化目标
|
||||
|
||||
不要只按总收益选择参数。1 分钟策略噪音大,应该综合看:
|
||||
|
||||
- 样本外收益为正。
|
||||
- 最大回撤尽量小。
|
||||
- Profit Factor 大于 1.20。
|
||||
- 胜率不低于 40%,如果胜率低,则平均盈亏比必须明显高于 1.5。
|
||||
- 单月交易次数不能太少,建议每月至少 20 笔,否则统计意义不足。
|
||||
- 多空两边不能严重失衡,除非策略明确只适合单边行情。
|
||||
|
||||
参数选择优先级:
|
||||
|
||||
1. 样本外稳定性。
|
||||
2. 最大回撤。
|
||||
3. Profit Factor。
|
||||
4. 平均盈亏比。
|
||||
5. 总收益率。
|
||||
|
||||
### 防止过拟合
|
||||
|
||||
以下情况说明参数可能过拟合:
|
||||
|
||||
- 训练集收益很好,验证集和测试集明显变差。
|
||||
- 只有某一个月表现很好,其他月份表现一般。
|
||||
- 参数落在搜索范围边界,例如最大止损距离优化后总是取最大值。
|
||||
- 交易次数太少,靠少数几笔大盈利撑起收益。
|
||||
- 多次微调后收益提升,但回撤和稳定性变差。
|
||||
|
||||
处理方式:
|
||||
|
||||
- 选择参数平台区间,不选单个尖峰最优值。
|
||||
- 如果 0.20 * ATR、0.25 * ATR、0.30 * ATR 表现接近,优先选中间值。
|
||||
- 验证集表现比训练集差很多时,降低参数复杂度。
|
||||
- 每次只优化一组相关参数,例如先优化中枢和突破,再优化止损止盈。
|
||||
|
||||
### 推荐优化顺序
|
||||
|
||||
1. 先只测原始第三类买卖点,得到基准胜率和盈亏比。
|
||||
2. 加入中枢宽度过滤,观察交易次数和假突破是否下降。
|
||||
3. 加入离开力度和成交量过滤,优化胜率。
|
||||
4. 加入回抽质量过滤,减少追高追低。
|
||||
5. 加入大周期 EMA 过滤,观察震荡行情亏损是否下降。
|
||||
6. 优化止损距离和时间止损。
|
||||
7. 最后比较止盈方式:固定 2R、1R 减半 2R 全平、2R 后跟踪止盈。
|
||||
|
||||
每一步都要和上一步对比,只保留能提升样本外表现的过滤条件。
|
||||
|
||||
### 回测命令示例
|
||||
|
||||
先跑固定参数基准:
|
||||
|
||||
```bash
|
||||
freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250101-20250630
|
||||
```
|
||||
|
||||
再按训练集、验证集、测试集分别跑:
|
||||
|
||||
```bash
|
||||
freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250101-20250630
|
||||
freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250701-20250831
|
||||
freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250901-20251031
|
||||
```
|
||||
|
||||
如果后续把参数写成 Freqtrade 的可优化参数,可以使用 hyperopt 搜索核心参数,但最终仍然要用样本外测试集确认。
|
||||
|
||||
## 回测观察指标
|
||||
|
||||
回测时重点观察:
|
||||
|
||||
- 三买和三卖分别的胜率。
|
||||
- 趋势开始、中期、末期三个阶段的收益差异。
|
||||
- 止损距离过大的交易是否拖累整体收益。
|
||||
- 震荡行情中过滤条件是否能减少假突破。
|
||||
- 1R 减半和 2R 全平是否优于一次性止盈。
|
||||
|
||||
## 策略总结
|
||||
|
||||
这套策略只做确认后的 1 分钟第三类买卖点,不提前猜测。1 分钟纯笔中枢负责保留足够完整的结构事实,允许相邻中枢连续出现;交易层再通过大周期方向、中枢宽度、离开力度、回抽质量和止损距离过滤掉低质量三买三卖。核心不是在中枢层过早删除结构,而是让 1 分钟找点、5 分钟定环境。
|
||||
@@ -0,0 +1,213 @@
|
||||
# --- Do not remove these libs ---
|
||||
from statistics import median
|
||||
from freqtrade.strategy import IStrategy, stoploss_from_absolute
|
||||
import sys
|
||||
import os
|
||||
# 添加父目录到系统路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from ChanLun import ChanLun
|
||||
from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX, Chan_BSP_TYPE
|
||||
# --------------------------------
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
### Now you can use logger.info('asfd') to log
|
||||
# freqtrade plot-dataframe --strategy ChanLun_BTC_1m --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309-
|
||||
|
||||
# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies
|
||||
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20260501-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_1m.json -e 200 --timerange=20250201-20250901
|
||||
# freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
|
||||
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250721-
|
||||
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
|
||||
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies
|
||||
|
||||
class ChanLun_BTC_1m(IStrategy):
|
||||
"""
|
||||
交易核心(缠论):
|
||||
- 仅在缠论一/二/三类买卖点出现时交易。
|
||||
- 信号触发条件:前一笔被确认(bi.is_sure)时,该笔 end_klc 已被标记为 B1/B2/B3 或 S1/S2/S3。
|
||||
- 不使用未确认笔,不使用“状态猜测”列。
|
||||
"""
|
||||
INTERFACE_VERSION: int = 3
|
||||
timeframe = '1m'
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||
minimal_roi = {
|
||||
"0": 100
|
||||
}
|
||||
|
||||
can_short = True
|
||||
enable_long = True
|
||||
enable_short = False
|
||||
lev = 1.0
|
||||
stoploss = -0.3 # 兜底止损,实际由 custom_stoploss 基于中枢 zg/zd 控制
|
||||
use_custom_stoploss = True
|
||||
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = 0.03
|
||||
trailing_stop_positive_offset = 0.06
|
||||
trailing_only_offset_is_reached = False
|
||||
use_exit_signal = True
|
||||
position_adjustment_enable = True
|
||||
startup_candle_count = 500
|
||||
# 以 1m 为基础周期时,1h = 60 根K线(用于读取 resample_60_* 列并做确认延迟)
|
||||
chan = ChanLun()
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe = self.add_indicators(dataframe)
|
||||
bsp_signal_data = self.chan.get_bsp_signal_data(dataframe)
|
||||
for column, values in bsp_signal_data.items():
|
||||
dataframe[column] = values
|
||||
return dataframe
|
||||
def add_indicators(self, df):
|
||||
df = self.add_base_indicators(df)
|
||||
base_interval = self.get_ticker_indicator()
|
||||
for interval in (5, 15, 60):
|
||||
if interval <= base_interval:
|
||||
df = self.copy_base_indicators_to_resample(df, interval)
|
||||
continue
|
||||
resampled = resample_to_interval(df, interval)
|
||||
resampled = self.add_base_indicators(resampled)
|
||||
df = resampled_merge(df, resampled)
|
||||
return df
|
||||
def copy_base_indicators_to_resample(self, df, interval):
|
||||
prefix = f'resample_{interval}_'
|
||||
for column in (
|
||||
'date', 'open', 'high', 'low', 'close', 'volume',
|
||||
'atr', 'macd', 'macdsignal', 'macdhist', 'ema24', 'ema52',
|
||||
'atr_ratio', 'resistance_240', 'support_240', 'trend'
|
||||
):
|
||||
if column in df.columns:
|
||||
df[f'{prefix}{column}'] = df[column]
|
||||
return df
|
||||
def add_base_indicators(self, df):
|
||||
fast = 12
|
||||
slow = 26
|
||||
period = 9
|
||||
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
df['ema24'] = ta.EMA(df, timeperiod=24)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
df['atr_ratio'] = df['atr'] / df['close']
|
||||
df['resistance_240'] = df['high'].rolling(240).max().shift(1)
|
||||
df['support_240'] = df['low'].rolling(240).min().shift(1)
|
||||
df['trend'] = 0
|
||||
df.loc[(df['close'] > df['ema52']) & (df['ema24'] >= df['ema52']), 'trend'] = 1
|
||||
df.loc[(df['close'] < df['ema52']) & (df['ema24'] <= df['ema52']), 'trend'] = -1
|
||||
return df
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
min_atr_ratio = 0.0005
|
||||
long_min_sr_distance_r = 1.0
|
||||
short_min_sr_distance_r = 0.8
|
||||
long_space_ratio = (dataframe['resistance_240'].shift(1) - dataframe['close'].shift(1)) / dataframe['close'].shift(1)
|
||||
short_space_ratio = (dataframe['close'].shift(1) - dataframe['support_240'].shift(1)) / dataframe['close'].shift(1)
|
||||
# 多周期趋势共振:3个周期中至少2个同向(而非全部3个)
|
||||
long_tf_aligned = (
|
||||
(dataframe['resample_5_trend'].shift(1) == 1).astype(int) +
|
||||
(dataframe['resample_15_trend'].shift(1) == 1).astype(int) +
|
||||
(dataframe['resample_60_trend'].shift(1) == 1).astype(int)
|
||||
) >= 2
|
||||
short_tf_aligned = (
|
||||
(dataframe['resample_5_trend'].shift(1) == -1).astype(int) +
|
||||
(dataframe['resample_15_trend'].shift(1) == -1).astype(int) +
|
||||
(dataframe['resample_60_trend'].shift(1) == -1).astype(int)
|
||||
) >= 2
|
||||
dataframe.loc[
|
||||
(
|
||||
self.enable_long &
|
||||
(dataframe['bsp_state'].shift(1) == -1) &
|
||||
(dataframe['bsp_risk_ratio'].shift(1) > 0) &
|
||||
(long_space_ratio >= dataframe['bsp_risk_ratio'].shift(1) * long_min_sr_distance_r) &
|
||||
(dataframe['macdhist'].shift(1) > 0) &
|
||||
(dataframe['atr_ratio'].shift(1) >= min_atr_ratio) &
|
||||
(dataframe['trend'].shift(1) == 1) &
|
||||
long_tf_aligned
|
||||
),
|
||||
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
|
||||
dataframe.loc[
|
||||
(
|
||||
self.enable_short &
|
||||
(dataframe['bsp_state'].shift(1) == 1) &
|
||||
(dataframe['bsp_risk_ratio'].shift(1) > 0) &
|
||||
(short_space_ratio >= dataframe['bsp_risk_ratio'].shift(1) * short_min_sr_distance_r) &
|
||||
(dataframe['macdhist'].shift(1) < 0) &
|
||||
(dataframe['atr_ratio'].shift(1) >= min_atr_ratio) &
|
||||
(dataframe['trend'].shift(1) == -1) &
|
||||
short_tf_aligned
|
||||
),
|
||||
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||
return dataframe
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe['exit_long'] = 0
|
||||
dataframe['exit_short'] = 0
|
||||
return dataframe
|
||||
def get_trade_risk_ratio(self, pair: str, trade) -> float:
|
||||
risk_ratio = trade.get_custom_data('risk_ratio')
|
||||
if risk_ratio:
|
||||
return float(risk_ratio)
|
||||
|
||||
risk_ratio = 0.001
|
||||
try:
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
if len(dataframe) > 0:
|
||||
entry_rows = dataframe[dataframe['date'] <= trade.open_date_utc]
|
||||
entry_candle = entry_rows.iloc[-1] if len(entry_rows) > 0 else dataframe.iloc[-1]
|
||||
signal_rows = entry_rows.tail(3)
|
||||
signal_rows = signal_rows[signal_rows['bsp_risk_ratio'] > 0]
|
||||
if len(signal_rows) > 0:
|
||||
signal_candle = signal_rows.iloc[-1]
|
||||
risk_ratio = float(signal_candle['bsp_risk_ratio'])
|
||||
trade.set_custom_data('bsp_stop_price', float(signal_candle['bsp_stop_price']))
|
||||
trade.set_custom_data('bsp_zg', float(signal_candle['bsp_zg']))
|
||||
trade.set_custom_data('bsp_zd', float(signal_candle['bsp_zd']))
|
||||
else:
|
||||
risk_ratio = max(0.001, min(float(entry_candle['atr_ratio']), 0.005))
|
||||
except Exception:
|
||||
risk_ratio = 0.001
|
||||
|
||||
trade.set_custom_data('risk_ratio', risk_ratio)
|
||||
return risk_ratio
|
||||
def adjust_trade_position(self, trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float,
|
||||
min_stake: float | None, max_stake: float,
|
||||
current_entry_rate: float, current_exit_rate: float,
|
||||
current_entry_profit: float, current_exit_profit: float,
|
||||
**kwargs):
|
||||
risk_ratio = self.get_trade_risk_ratio(trade.pair, trade)
|
||||
if current_profit >= risk_ratio and trade.nr_of_successful_exits == 0:
|
||||
return -(trade.stake_amount / 2), 'take_half_1r'
|
||||
return None
|
||||
def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, **kwargs):
|
||||
risk_ratio = self.get_trade_risk_ratio(pair, trade)
|
||||
if trade.nr_of_successful_exits > 0 and current_profit <= 0.001:
|
||||
return 'breakeven_after_1r'
|
||||
if current_profit >= risk_ratio * 2:
|
||||
return 'take_profit_2r'
|
||||
return None
|
||||
def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, after_fill: bool, **kwargs) -> float | None:
|
||||
bsp_stop_price = trade.get_custom_data('bsp_stop_price')
|
||||
if bsp_stop_price:
|
||||
sl = stoploss_from_absolute(float(bsp_stop_price), current_rate, is_short=trade.is_short)
|
||||
return min(sl, -0.05)
|
||||
return -0.05
|
||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
|
||||
**kwargs) -> float:
|
||||
return self.lev
|
||||
def get_ticker_indicator(self):
|
||||
return int(self.timeframe[:-1])
|
||||
@@ -0,0 +1,201 @@
|
||||
# --- Do not remove these libs ---
|
||||
from statistics import median
|
||||
from freqtrade.strategy import IStrategy, stoploss_from_absolute
|
||||
import sys
|
||||
import os
|
||||
# 添加父目录到系统路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from ChanLun import ChanLun
|
||||
from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX, Chan_BSP_TYPE
|
||||
# --------------------------------
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ChanLun_BTC_5m(IStrategy):
|
||||
"""ChanLun_BTC_5m: 5m B3 signals with trailing stop exit."""
|
||||
|
||||
INTERFACE_VERSION: int = 3
|
||||
timeframe = '5m'
|
||||
minimal_roi = {"0": 100}
|
||||
|
||||
can_short = True
|
||||
enable_long = True
|
||||
enable_short = False
|
||||
lev = 1.0
|
||||
stoploss = -0.3
|
||||
use_custom_stoploss = True
|
||||
|
||||
trailing_stop = False
|
||||
use_exit_signal = True
|
||||
position_adjustment_enable = False
|
||||
startup_candle_count = 500
|
||||
chan = ChanLun()
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe = self.add_indicators(dataframe)
|
||||
bsp_signal_data = self.chan.get_bsp_signal_data(dataframe)
|
||||
for column, values in bsp_signal_data.items():
|
||||
dataframe[column] = values
|
||||
return dataframe
|
||||
|
||||
def add_indicators(self, df):
|
||||
df = self.add_base_indicators(df)
|
||||
base_interval = self.get_ticker_indicator()
|
||||
for interval in (5, 15, 60):
|
||||
if interval <= base_interval:
|
||||
df = self.copy_base_indicators_to_resample(df, interval)
|
||||
continue
|
||||
resampled = resample_to_interval(df, interval)
|
||||
resampled = self.add_base_indicators(resampled)
|
||||
df = resampled_merge(df, resampled)
|
||||
return df
|
||||
|
||||
def copy_base_indicators_to_resample(self, df, interval):
|
||||
prefix = f'resample_{interval}_'
|
||||
for column in (
|
||||
'date', 'open', 'high', 'low', 'close', 'volume',
|
||||
'atr', 'macd', 'macdsignal', 'macdhist', 'ema24', 'ema52',
|
||||
'atr_ratio', 'resistance_240', 'support_240', 'trend'
|
||||
):
|
||||
if column in df.columns:
|
||||
df[f'{prefix}{column}'] = df[column]
|
||||
return df
|
||||
|
||||
def add_base_indicators(self, df):
|
||||
fast = 12
|
||||
slow = 26
|
||||
period = 9
|
||||
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
df['ema24'] = ta.EMA(df, timeperiod=24)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
df['atr_ratio'] = df['atr'] / df['close']
|
||||
df['resistance_240'] = df['high'].rolling(240).max().shift(1)
|
||||
df['support_240'] = df['low'].rolling(240).min().shift(1)
|
||||
df['trend'] = 0
|
||||
df.loc[(df['close'] > df['ema52']) & (df['ema24'] >= df['ema52']), 'trend'] = 1
|
||||
df.loc[(df['close'] < df['ema52']) & (df['ema24'] <= df['ema52']), 'trend'] = -1
|
||||
return df
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
min_atr_ratio = 0.0005
|
||||
long_min_sr_distance_r = 1.0
|
||||
short_min_sr_distance_r = 0.8
|
||||
long_space_ratio = (dataframe['resistance_240'].shift(1) - dataframe['close'].shift(1)) / dataframe['close'].shift(1)
|
||||
short_space_ratio = (dataframe['close'].shift(1) - dataframe['support_240'].shift(1)) / dataframe['close'].shift(1)
|
||||
long_tf_aligned = (
|
||||
(dataframe['resample_5_trend'].shift(1) == 1).astype(int) +
|
||||
(dataframe['resample_15_trend'].shift(1) == 1).astype(int) +
|
||||
(dataframe['resample_60_trend'].shift(1) == 1).astype(int)
|
||||
) >= 2
|
||||
short_tf_aligned = (
|
||||
(dataframe['resample_5_trend'].shift(1) == -1).astype(int) +
|
||||
(dataframe['resample_15_trend'].shift(1) == -1).astype(int) +
|
||||
(dataframe['resample_60_trend'].shift(1) == -1).astype(int)
|
||||
) >= 2
|
||||
dataframe.loc[
|
||||
(
|
||||
self.enable_long &
|
||||
(dataframe['bsp_state'].shift(1) == -1) &
|
||||
(dataframe['bsp_risk_ratio'].shift(1) > 0) &
|
||||
(long_space_ratio >= dataframe['bsp_risk_ratio'].shift(1) * long_min_sr_distance_r) &
|
||||
(dataframe['macdhist'].shift(1) > 0) &
|
||||
(dataframe['atr_ratio'].shift(1) >= min_atr_ratio) &
|
||||
(dataframe['trend'].shift(1) == 1) &
|
||||
long_tf_aligned
|
||||
),
|
||||
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
|
||||
dataframe.loc[
|
||||
(
|
||||
self.enable_short &
|
||||
(dataframe['bsp_state'].shift(1) == 1) &
|
||||
(dataframe['bsp_risk_ratio'].shift(1) > 0) &
|
||||
(short_space_ratio >= dataframe['bsp_risk_ratio'].shift(1) * short_min_sr_distance_r) &
|
||||
(dataframe['macdhist'].shift(1) < 0) &
|
||||
(dataframe['atr_ratio'].shift(1) >= min_atr_ratio) &
|
||||
(dataframe['trend'].shift(1) == -1) &
|
||||
short_tf_aligned
|
||||
),
|
||||
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe['exit_long'] = 0
|
||||
dataframe['exit_short'] = 0
|
||||
return dataframe
|
||||
|
||||
def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, **kwargs):
|
||||
# Time-based exit only - trailing stop handles profit taking
|
||||
elapsed = current_time - trade.open_date_utc
|
||||
if elapsed >= timedelta(hours=72) and current_profit < 0.005:
|
||||
return 'time_stop_72h'
|
||||
return None
|
||||
|
||||
def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, after_fill: bool, **kwargs) -> float | None:
|
||||
# Initialize stored state
|
||||
if not trade.get_custom_data('trail_activated'):
|
||||
trade.set_custom_data('trail_activated', False)
|
||||
trade.set_custom_data('max_profit', 0.0)
|
||||
# Read bsp_stop_price from signal
|
||||
try:
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
if len(dataframe) > 0:
|
||||
entry_rows = dataframe[dataframe['date'] <= trade.open_date_utc]
|
||||
signal_rows = entry_rows.tail(3)
|
||||
signal_rows = signal_rows[signal_rows['bsp_risk_ratio'] > 0]
|
||||
if len(signal_rows) > 0:
|
||||
signal_candle = signal_rows.iloc[-1]
|
||||
trade.set_custom_data('bsp_stop_price', float(signal_candle['bsp_stop_price']))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
max_profit = max(float(trade.get_custom_data('max_profit')), current_profit)
|
||||
trade.set_custom_data('max_profit', max_profit)
|
||||
trail_activated = trade.get_custom_data('trail_activated')
|
||||
|
||||
# Stage 1: Initial stop at bsp_stop with -5% floor
|
||||
if not trail_activated:
|
||||
if max_profit >= 0.02:
|
||||
# Activate trail: move stop to breakeven
|
||||
trade.set_custom_data('trail_activated', True)
|
||||
sl = stoploss_from_absolute(trade.open_rate, current_rate, is_short=trade.is_short)
|
||||
return max(sl, -0.005)
|
||||
else:
|
||||
bsp_stop = trade.get_custom_data('bsp_stop_price')
|
||||
if bsp_stop:
|
||||
sl = stoploss_from_absolute(float(bsp_stop), current_rate, is_short=trade.is_short)
|
||||
return min(sl, -0.05)
|
||||
return -0.05
|
||||
else:
|
||||
# Stage 2: Trail from max profit
|
||||
if max_profit >= 0.04:
|
||||
trail_offset = 0.02 # Trail 2% behind max
|
||||
trail_price = trade.open_rate * (1 + max_profit - trail_offset)
|
||||
sl = stoploss_from_absolute(trail_price, current_rate, is_short=trade.is_short)
|
||||
return max(sl, -0.02)
|
||||
elif max_profit >= 0.02:
|
||||
# Breakeven to 1% trail
|
||||
sl = stoploss_from_absolute(trade.open_rate * 1.005, current_rate, is_short=trade.is_short)
|
||||
return max(sl, -0.005)
|
||||
else:
|
||||
sl = stoploss_from_absolute(trade.open_rate * 0.998, current_rate, is_short=trade.is_short)
|
||||
return max(sl, -0.02)
|
||||
|
||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
|
||||
**kwargs) -> float:
|
||||
return self.lev
|
||||
|
||||
def get_ticker_indicator(self):
|
||||
return int(self.timeframe[:-1])
|
||||
@@ -0,0 +1,133 @@
|
||||
# --- Do not remove these libs ---
|
||||
from statistics import median
|
||||
from freqtrade.strategy import IStrategy, stoploss_from_absolute
|
||||
import sys
|
||||
import os
|
||||
# 添加父目录到系统路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from ChanLun import ChanLun
|
||||
from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX, Chan_BSP_TYPE
|
||||
# --------------------------------
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
### Now you can use logger.info('asfd') to log
|
||||
# freqtrade plot-dataframe --strategy ChanLun_BTC_1m --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309-
|
||||
|
||||
# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies
|
||||
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20260501-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_1m.json -e 200 --timerange=20250201-20250901
|
||||
# freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
|
||||
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies --timerange=20250721-
|
||||
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_1m.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
|
||||
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_BTC_1m.json --strategy ChanLun_BTC_1m --strategy-path ./user_data/Chan/strategies
|
||||
|
||||
class Template(IStrategy):
|
||||
"""
|
||||
交易核心(缠论):
|
||||
- 仅在缠论一/二/三类买卖点出现时交易。
|
||||
- 信号触发条件:前一笔被确认(bi.is_sure)时,该笔 end_klc 已被标记为 B1/B2/B3 或 S1/S2/S3。
|
||||
- 不使用未确认笔,不使用“状态猜测”列。
|
||||
"""
|
||||
INTERFACE_VERSION: int = 3
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||
# 30m and 1h
|
||||
|
||||
minimal_roi = {
|
||||
"0": 0.05,
|
||||
"60": 0.03,
|
||||
"120": 0.01,
|
||||
"180": 0
|
||||
}
|
||||
# 5m and 15m
|
||||
minimal_roi_1 = {
|
||||
"0": 0.1,
|
||||
"60": 0.05,
|
||||
"120": 0.02,
|
||||
"240": 0
|
||||
}
|
||||
# 15m and 30m
|
||||
minimal_roi_1 = {
|
||||
"0": 0.1,
|
||||
"240": 0.05,
|
||||
"480": 0.03,
|
||||
"600": 0
|
||||
}
|
||||
minimal_roi_1 = {
|
||||
"0": 1.50,
|
||||
"120": 0.05,
|
||||
"240": 0.025,
|
||||
"360": 0
|
||||
}
|
||||
|
||||
can_short = True
|
||||
lev = 1.0
|
||||
stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制
|
||||
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = 0.03
|
||||
trailing_stop_positive_offset = 0.06
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
# 关闭分批止盈/仓位调整
|
||||
startup_candle_count = 500
|
||||
# 以 1m 为基础周期时,1h = 60 根K线(用于读取 resample_60_* 列并做确认延迟)
|
||||
chan = ChanLun()
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe = self.add_indicators(dataframe)
|
||||
dataframe['bsp_state'] = self.chan.get_bsp_state(dataframe)
|
||||
return dataframe
|
||||
def add_indicators(self, df):
|
||||
fast = 12
|
||||
slow = 26
|
||||
period = 9
|
||||
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
df['ema24'] = ta.EMA(df, timeperiod=24)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
return df
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['bsp_state'].shift(1) == -1)
|
||||
),
|
||||
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['bsp_state'].shift(1) == 1)
|
||||
),
|
||||
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||
return dataframe
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# 出场和进场共用同一套“确认笔 + end_klc 买卖点”语义。
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['bsp_state'].shift(1) == 1)
|
||||
),
|
||||
['exit_long', 'exit_tag']] = (1, 'long_signal_chan')
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['bsp_state'].shift(1) == -1)
|
||||
),
|
||||
['exit_short', 'exit_tag']] = (1, 'short_signal_chan')
|
||||
return dataframe
|
||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
|
||||
**kwargs) -> float:
|
||||
return self.lev
|
||||
def get_ticker_indicator(self):
|
||||
return int(self.timeframe[:-1])
|
||||
Reference in New Issue
Block a user