Add freqtrade quant strategies and deploy docs
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"max_open_trades": 2,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": false,
|
||||
"timeframe": "15m",
|
||||
"stoploss": -0.80,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"collateral": "USDT",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "ask",
|
||||
"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,
|
||||
"price_last_balance": 0.0
|
||||
},
|
||||
"exchange": {
|
||||
"name": "okx",
|
||||
"key": "e756108d-f14c-4777-8351-cd989f389dd2",
|
||||
"secret": "BC4579AEE9816ED4A38218910B6EC278",
|
||||
"password": "30l9L666.",
|
||||
"ccxt_config": {
|
||||
"hostname": "www.okx.cab"
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"hostname": "www.okx.cab"
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": []
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "somethingRandomSomethingRandom123",
|
||||
"CORS_origins": [
|
||||
"*"
|
||||
],
|
||||
"username": "liam",
|
||||
"password": "30l9L666"
|
||||
},
|
||||
"bot_name": "freqtrade",
|
||||
"force_entry_enable": true,
|
||||
"webhook": {
|
||||
"enabled": true,
|
||||
"url": "https://open.feishu.cn/open-apis/bot/v2/hook/2b1bc9a6-b470-447f-b783-1b253dcf522c",
|
||||
"format": "json",
|
||||
"timeout": 10,
|
||||
"entry": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "🟢 交易信号: {pair}",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "blue"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "方向: {direction}\n入场价: {open_rate}\n金额: {stake_amount:.2f} {stake_currency}\n杠杆: {leverage}x\n信号: {enter_tag}",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "hr"
|
||||
},
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "时间: {open_date}",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"entry_fill": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "✅ 入场成交: {pair}",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "green"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "成交价: {open_rate}\n数量: {amount}\n金额: {stake_amount:.2f} {stake_currency}\n杠杆: {leverage}x",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"exit": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "🔴 退出信号: {pair}",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "red"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "当前价: {current_rate}\n盈亏率: {profit_ratio:.2%}\n原因: {exit_reason}",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"exit_fill": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "✅ 退出成交: {pair}",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "green"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "退出价: {close_rate}\n盈亏率: {profit_ratio:.2%}\n盈亏额: {profit_amount:.4f}\n原因: {exit_reason}",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"entry_cancel": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "⚠ 入场取消: {pair}",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "yellow"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"content": "原因: {reason}\n数量: {amount}\n价格: {open_rate}",
|
||||
"tag": "lark_md"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initial_state": "running",
|
||||
"strategy": "MultiAssetChannelBreakoutV5",
|
||||
"strategy_path": "user_data/strategies/"
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"max_open_trades": 5,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": false,
|
||||
"timeframe": "5m",
|
||||
"stoploss": -0.10,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"collateral": "USDT",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "ask",
|
||||
"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,
|
||||
"price_last_balance": 0.0
|
||||
},
|
||||
"exchange": {
|
||||
"name": "okx",
|
||||
"key": "e756108d-f14c-4777-8351-cd989f389dd2",
|
||||
"secret": "BC4579AEE9816ED4A38218910B6EC278",
|
||||
"password": "30l9L666.",
|
||||
"ccxt_config": {
|
||||
"hostname": "www.okx.cab"
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"hostname": "www.okx.cab"
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"TRX/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": []
|
||||
},
|
||||
"pairlists": [
|
||||
{"method": "StaticPairList"}
|
||||
],
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "somethingRandomSomethingRandom123",
|
||||
"CORS_origins": ["*"],
|
||||
"username": "liam",
|
||||
"password": "30l9L666"
|
||||
},
|
||||
"bot_name": "freqtrade",
|
||||
"force_entry_enable": true,
|
||||
"webhook": {
|
||||
"enabled": true,
|
||||
"url": "https://open.feishu.cn/open-apis/bot/v2/hook/2b1bc9a6-b470-447f-b783-1b253dcf522c",
|
||||
"format": "json",
|
||||
"timeout": 10,
|
||||
"entry": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {"content": "🟢 交易信号: {pair}", "tag": "plain_text"},
|
||||
"template": "blue"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"content": "方向: {direction}\n入场价: {open_rate}\n金额: {stake_amount:.2f} {stake_currency}\n杠杆: {leverage}x\n信号: {enter_tag}", "tag": "lark_md"}},
|
||||
{"tag": "hr"},
|
||||
{"tag": "div", "text": {"content": "时间: {open_date}", "tag": "lark_md"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"entry_fill": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {"content": "✅ 入场成交: {pair}", "tag": "plain_text"},
|
||||
"template": "green"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"content": "成交价: {open_rate}\n数量: {amount}\n金额: {stake_amount:.2f} {stake_currency}\n杠杆: {leverage}x", "tag": "lark_md"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"exit": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {"content": "🔴 退出信号: {pair}", "tag": "plain_text"},
|
||||
"template": "red"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"content": "当前价: {current_rate}\n盈亏率: {profit_ratio:.2%}\n原因: {exit_reason}", "tag": "lark_md"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"exit_fill": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {"content": "✅ 退出成交: {pair}", "tag": "plain_text"},
|
||||
"template": "green"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"content": "退出价: {close_rate}\n盈亏率: {profit_ratio:.2%}\n盈亏额: {profit_amount:.4f}\n原因: {exit_reason}", "tag": "lark_md"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"entry_cancel": {
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"header": {
|
||||
"title": {"content": "⚠ 入场取消: {pair}", "tag": "plain_text"},
|
||||
"template": "yellow"
|
||||
},
|
||||
"elements": [
|
||||
{"tag": "div", "text": {"content": "原因: {reason}\n数量: {amount}\n价格: {open_rate}", "tag": "lark_md"}}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initial_state": "running",
|
||||
"strategy": "QuantDingerStrategy",
|
||||
"strategy_path": "user_data/strategies/"
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from functools import reduce
|
||||
from datetime import datetime
|
||||
from freqtrade.strategy import (IStrategy, IntParameter, DecimalParameter)
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
class MultiAssetChannelBreakoutV5(IStrategy):
|
||||
"""
|
||||
多币种通道突破策略 V5。
|
||||
|
||||
策略核心思想:
|
||||
1) 用 ATR 构建波动率自适应通道,避免固定阈值在不同波动阶段失效;
|
||||
2) 用长周期均线 + ADX 过滤震荡,仅在趋势和动量共振时入场;
|
||||
3) 用动态下轨出场 + 自定义分段止损,兼顾跟踪利润与回撤控制;
|
||||
4) 用入场二次确认减少信号触发后追高与滑点风险。
|
||||
|
||||
说明:
|
||||
- 当前默认参数来自 `MultiAssetChannelBreakoutV3.json` 的优化结果;
|
||||
- 策略为单向做多(can_short=False),期货模式下通过 leverage() 固定 10x。
|
||||
"""
|
||||
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# --- 1) 基础交易设置 ---
|
||||
# 关闭 ROI 快速止盈,完全依赖信号出场 + custom_stoploss 风控。
|
||||
minimal_roi = {"0": 100}
|
||||
# 硬止损作为最后防线(保证金维度 -80% = 价格 -8%,10x 杠杆下接近爆仓线但能扛住正常波动)。
|
||||
stoploss = -0.80
|
||||
trailing_stop = False
|
||||
use_custom_stoploss = True
|
||||
timeframe = "15m"
|
||||
can_short = False
|
||||
# 预热 K 线数量:覆盖长均线与通道计算窗口,避免初期信号失真。
|
||||
startup_candle_count = 600
|
||||
|
||||
# --- 2) 可优化参数(默认值已填入优化结果) ---
|
||||
# 上轨窗口:越大越平滑,越小越敏感。
|
||||
up_line_span = IntParameter(100, 400, default=340, space="buy", optimize=True)
|
||||
# 下轨窗口:用于退出通道底边计算。
|
||||
buy_stop_profit_span = IntParameter(50, 200, default=141, space="buy", optimize=True)
|
||||
# 长均线天数(会换算成 15m 周期)。
|
||||
ma_span_long_days = IntParameter(1, 5, default=2, space="buy", optimize=True)
|
||||
# 动量阈值:过滤弱趋势。
|
||||
adx_threshold = IntParameter(15, 40, default=28, space="buy", optimize=True)
|
||||
# 上轨波动率偏移:ATR * offset。
|
||||
up_line_offset = DecimalParameter(-0.2, 0.4, default=-0.18, decimals=2, space="buy", optimize=True)
|
||||
|
||||
# 下轨波动率偏移:ATR * offset,用于构建动态退出线。
|
||||
buy_stop_profit_offset = DecimalParameter(-0.2, 0.2, default=0.19, decimals=2, space="sell", optimize=True)
|
||||
|
||||
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
计算策略使用的全部指标。
|
||||
|
||||
指标列表:
|
||||
- n_value: ATR(10),用于描述近期真实波动率;
|
||||
- ma_long: 长周期均线(天数 * 96 根 15m K);
|
||||
- up_line: 历史高点通道上轨 + ATR 偏移;
|
||||
- bottom_line: 历史低点通道下轨;
|
||||
- adx: ADX(14) 动量强度指标。
|
||||
"""
|
||||
dataframe["n_value"] = ta.ATR(dataframe, timeperiod=10)
|
||||
|
||||
ma_length = self.ma_span_long_days.value * 96
|
||||
dataframe["ma_long"] = ta.SMA(dataframe, timeperiod=ma_length)
|
||||
|
||||
base_up_line = dataframe["high"].rolling(window=self.up_line_span.value).max().shift(1)
|
||||
dataframe["up_line"] = base_up_line + (dataframe["n_value"] * self.up_line_offset.value)
|
||||
|
||||
dataframe["bottom_line"] = dataframe["low"].rolling(window=self.buy_stop_profit_span.value).min().shift(1)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
入场逻辑(做多):
|
||||
- 指标有效(ATR、上轨非空);
|
||||
- 收盘价在长均线上方(趋势过滤);
|
||||
- ADX 高于阈值(动量过滤);
|
||||
- 收盘价突破动态上轨(突破确认)。
|
||||
"""
|
||||
conditions = []
|
||||
conditions.append(dataframe["n_value"].notnull())
|
||||
conditions.append(dataframe["up_line"].notnull())
|
||||
conditions.append(dataframe["close"] > dataframe["ma_long"])
|
||||
conditions.append(dataframe["adx"] > self.adx_threshold.value)
|
||||
conditions.append(dataframe["close"] > dataframe["up_line"])
|
||||
|
||||
if conditions:
|
||||
is_entry = reduce(lambda x, y: x & y, conditions)
|
||||
dataframe.loc[is_entry, "enter_long"] = 1
|
||||
dataframe.loc[is_entry, "enter_tag"] = "trend_breakout"
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
出场逻辑:
|
||||
- 构建动态退出线:bottom_line + ATR * buy_stop_profit_offset;
|
||||
- 收盘价跌破退出线时触发平仓信号。
|
||||
"""
|
||||
offset = self.buy_stop_profit_offset.value
|
||||
exit_line = dataframe["bottom_line"] + (dataframe["n_value"] * offset)
|
||||
|
||||
exit_condition = dataframe["close"] < exit_line
|
||||
dataframe.loc[exit_condition, "exit_long"] = 1
|
||||
dataframe.loc[exit_condition, "exit_tag"] = "channel_exit"
|
||||
return dataframe
|
||||
|
||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_leverage: float, max_leverage: float, entry_tag: str, side: str,
|
||||
**kwargs) -> float:
|
||||
"""
|
||||
统一固定 10x 杠杆。
|
||||
"""
|
||||
return 10.0
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
"""
|
||||
分段动态止损(所有阈值通过 trade.leverage 动态联动杠杆):
|
||||
- 盈利 > 价格+30%: 允许 10% 价格回撤;
|
||||
- 盈利 > 价格+15%: 允许 10% 价格回撤;
|
||||
- 盈利 > 价格+5%: 将止损上移到保本上方约 5%(覆盖手续费与滑点);
|
||||
- 其余阶段: 交由全局硬止损处理。
|
||||
"""
|
||||
L = trade.leverage
|
||||
|
||||
if current_profit > 0.30 * L:
|
||||
return 0.10 * L
|
||||
|
||||
if current_profit > 0.15 * L:
|
||||
return 0.10 * L
|
||||
|
||||
if current_profit > 0.05 * L:
|
||||
breakeven_target = trade.open_rate * 1.05
|
||||
return (current_rate - breakeven_target) / current_rate * L
|
||||
|
||||
return L
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime, entry_tag: str,
|
||||
side: str, **kwargs) -> bool:
|
||||
"""
|
||||
入场二次确认(防追高与防假突破):
|
||||
- 取触发信号的已收盘 K 线 (iloc[-2]) 作为基准;
|
||||
- 实时下单价不能低穿信号 K 线的均线和上轨(防假突破被瞬间砸回);
|
||||
- 下单价较信号 K 线收盘价高出 0.5% 以上,放弃交易(防滑点追高)。
|
||||
"""
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
|
||||
# 确保数据量足够回溯
|
||||
if len(dataframe) < 3:
|
||||
return False
|
||||
|
||||
# --- 1. 动态寻找真正的"信号 K 线" ---
|
||||
# 解决 Freqtrade 实盘中 DataFrame 最后一根 K 线可能是正在运行的新 K 线,
|
||||
# 也可能是刚刚收盘的信号 K 线的"索引漂移"问题。
|
||||
signal_candle = None
|
||||
|
||||
# 倒序检查最后 3 根 K 线,定位打上 enter_long=1 标签的那一根
|
||||
for i in range(-1, -4, -1):
|
||||
if dataframe['enter_long'].iloc[i] == 1:
|
||||
signal_candle = dataframe.iloc[i]
|
||||
break
|
||||
|
||||
# 如果最近几根都没信号(比如极端网络延迟造成的陈旧发单),果断放弃交易
|
||||
if signal_candle is None:
|
||||
return False
|
||||
|
||||
# --- 2. 核心校验:必须与信号当期的指标进行对比 ---
|
||||
# 解决"突破极值瞬间跳升"导致订单被错杀的问题
|
||||
|
||||
# 防假突破被瞬间砸回:实时下单价 (rate) 不能低穿【信号发生时】的均线和上轨
|
||||
if rate <= signal_candle["ma_long"]:
|
||||
return False
|
||||
if rate <= signal_candle["up_line"]:
|
||||
return False
|
||||
|
||||
# 防滑点追高:实时下单价 (rate) 较【信号发生时】的收盘价不能偏离过高
|
||||
# 注意:此处允许 1% 的追高滑点,如果在 15m 级别觉得 1% 的滑点成本太大,可以改为 1.005 (0.5%)
|
||||
if rate > signal_candle["close"] * 1.01:
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,187 @@
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from functools import reduce
|
||||
from datetime import datetime
|
||||
from freqtrade.strategy import (IStrategy, IntParameter, DecimalParameter)
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
class MultiAssetChannelBreakoutV5(IStrategy):
|
||||
"""
|
||||
多币种通道突破策略 V5。
|
||||
|
||||
策略核心思想:
|
||||
1) 用 ATR 构建波动率自适应通道,避免固定阈值在不同波动阶段失效;
|
||||
2) 用长周期均线 + ADX 过滤震荡,仅在趋势和动量共振时入场;
|
||||
3) 用动态下轨出场 + 自定义分段止损,兼顾跟踪利润与回撤控制;
|
||||
4) 用入场二次确认减少信号触发后追高与滑点风险。
|
||||
|
||||
说明:
|
||||
- 当前默认参数来自 `MultiAssetChannelBreakoutV3.json` 的优化结果;
|
||||
- 策略为单向做多(can_short=False),期货模式下通过 leverage() 固定 10x。
|
||||
"""
|
||||
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# --- 1) 基础交易设置 ---
|
||||
# 关闭 ROI 快速止盈,完全依赖信号出场 + custom_stoploss 风控。
|
||||
minimal_roi = {"0": 100}
|
||||
# 硬止损作为最后防线(保证金维度 -80% = 价格 -8%,10x 杠杆下接近爆仓线但能扛住正常波动)。
|
||||
stoploss = -0.80
|
||||
trailing_stop = False
|
||||
use_custom_stoploss = True
|
||||
timeframe = "15m"
|
||||
can_short = False
|
||||
# 预热 K 线数量:覆盖长均线与通道计算窗口,避免初期信号失真。
|
||||
startup_candle_count = 600
|
||||
|
||||
# --- 2) 可优化参数(默认值已填入优化结果) ---
|
||||
# 上轨窗口:越大越平滑,越小越敏感。
|
||||
up_line_span = IntParameter(100, 400, default=340, space="buy", optimize=True)
|
||||
# 下轨窗口:用于退出通道底边计算。
|
||||
buy_stop_profit_span = IntParameter(50, 200, default=141, space="buy", optimize=True)
|
||||
# 长均线天数(会换算成 15m 周期)。
|
||||
ma_span_long_days = IntParameter(1, 5, default=2, space="buy", optimize=True)
|
||||
# 动量阈值:过滤弱趋势。
|
||||
adx_threshold = IntParameter(15, 40, default=28, space="buy", optimize=True)
|
||||
# 上轨波动率偏移:ATR * offset。
|
||||
up_line_offset = DecimalParameter(-0.2, 0.4, default=-0.18, decimals=2, space="buy", optimize=True)
|
||||
|
||||
# 下轨波动率偏移:ATR * offset,用于构建动态退出线。
|
||||
buy_stop_profit_offset = DecimalParameter(-0.2, 0.2, default=0.19, decimals=2, space="sell", optimize=True)
|
||||
|
||||
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
计算策略使用的全部指标。
|
||||
|
||||
指标列表:
|
||||
- n_value: ATR(10),用于描述近期真实波动率;
|
||||
- ma_long: 长周期均线(天数 * 96 根 15m K);
|
||||
- up_line: 历史高点通道上轨 + ATR 偏移;
|
||||
- bottom_line: 历史低点通道下轨;
|
||||
- adx: ADX(14) 动量强度指标。
|
||||
"""
|
||||
dataframe["n_value"] = ta.ATR(dataframe, timeperiod=10)
|
||||
|
||||
ma_length = self.ma_span_long_days.value * 96
|
||||
dataframe["ma_long"] = ta.SMA(dataframe, timeperiod=ma_length)
|
||||
|
||||
base_up_line = dataframe["high"].rolling(window=self.up_line_span.value).max().shift(1)
|
||||
dataframe["up_line"] = base_up_line + (dataframe["n_value"] * self.up_line_offset.value)
|
||||
|
||||
dataframe["bottom_line"] = dataframe["low"].rolling(window=self.buy_stop_profit_span.value).min().shift(1)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
入场逻辑(做多):
|
||||
- 指标有效(ATR、上轨非空);
|
||||
- 收盘价在长均线上方(趋势过滤);
|
||||
- ADX 高于阈值(动量过滤);
|
||||
- 收盘价突破动态上轨(突破确认)。
|
||||
"""
|
||||
conditions = []
|
||||
conditions.append(dataframe["n_value"].notnull())
|
||||
conditions.append(dataframe["up_line"].notnull())
|
||||
conditions.append(dataframe["close"] > dataframe["ma_long"])
|
||||
conditions.append(dataframe["adx"] > self.adx_threshold.value)
|
||||
conditions.append(dataframe["close"] > dataframe["up_line"])
|
||||
|
||||
if conditions:
|
||||
is_entry = reduce(lambda x, y: x & y, conditions)
|
||||
dataframe.loc[is_entry, "enter_long"] = 1
|
||||
dataframe.loc[is_entry, "enter_tag"] = "trend_breakout"
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
"""
|
||||
出场逻辑:
|
||||
- 构建动态退出线:bottom_line + ATR * buy_stop_profit_offset;
|
||||
- 收盘价跌破退出线时触发平仓信号。
|
||||
"""
|
||||
offset = self.buy_stop_profit_offset.value
|
||||
exit_line = dataframe["bottom_line"] + (dataframe["n_value"] * offset)
|
||||
|
||||
exit_condition = dataframe["close"] < exit_line
|
||||
dataframe.loc[exit_condition, "exit_long"] = 1
|
||||
dataframe.loc[exit_condition, "exit_tag"] = "channel_exit"
|
||||
return dataframe
|
||||
|
||||
def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_leverage: float, max_leverage: float, entry_tag: str, side: str,
|
||||
**kwargs) -> float:
|
||||
"""
|
||||
统一固定 10x 杠杆。
|
||||
"""
|
||||
return 10.0
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
"""
|
||||
分段动态止损(所有阈值通过 trade.leverage 动态联动杠杆):
|
||||
- 盈利 > 价格+30%: 允许 10% 价格回撤;
|
||||
- 盈利 > 价格+15%: 允许 10% 价格回撤;
|
||||
- 盈利 > 价格+5%: 将止损上移到保本上方约 5%(覆盖手续费与滑点);
|
||||
- 其余阶段: 交由全局硬止损处理。
|
||||
"""
|
||||
L = trade.leverage
|
||||
|
||||
if current_profit > 0.30 * L:
|
||||
return 0.10 * L
|
||||
|
||||
if current_profit > 0.15 * L:
|
||||
return 0.10 * L
|
||||
|
||||
if current_profit > 0.05 * L:
|
||||
breakeven_target = trade.open_rate * 1.05
|
||||
return (current_rate - breakeven_target) / current_rate * L
|
||||
|
||||
return L
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime, entry_tag: str,
|
||||
side: str, **kwargs) -> bool:
|
||||
"""
|
||||
入场二次确认(防追高与防假突破):
|
||||
- 取触发信号的已收盘 K 线 (iloc[-2]) 作为基准;
|
||||
- 实时下单价不能低穿信号 K 线的均线和上轨(防假突破被瞬间砸回);
|
||||
- 下单价较信号 K 线收盘价高出 0.5% 以上,放弃交易(防滑点追高)。
|
||||
"""
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
|
||||
# 确保数据量足够回溯
|
||||
if len(dataframe) < 3:
|
||||
return False
|
||||
|
||||
# --- 1. 动态寻找真正的"信号 K 线" ---
|
||||
# 解决 Freqtrade 实盘中 DataFrame 最后一根 K 线可能是正在运行的新 K 线,
|
||||
# 也可能是刚刚收盘的信号 K 线的"索引漂移"问题。
|
||||
signal_candle = None
|
||||
|
||||
# 倒序检查最后 3 根 K 线,定位打上 enter_long=1 标签的那一根
|
||||
for i in range(-1, -4, -1):
|
||||
if dataframe['enter_long'].iloc[i] == 1:
|
||||
signal_candle = dataframe.iloc[i]
|
||||
break
|
||||
|
||||
# 如果最近几根都没信号(比如极端网络延迟造成的陈旧发单),果断放弃交易
|
||||
if signal_candle is None:
|
||||
return False
|
||||
|
||||
# --- 2. 核心校验:必须与信号当期的指标进行对比 ---
|
||||
# 解决"突破极值瞬间跳升"导致订单被错杀的问题
|
||||
|
||||
# 防假突破被瞬间砸回:实时下单价 (rate) 不能低穿【信号发生时】的均线和上轨
|
||||
if rate <= signal_candle["ma_long"]:
|
||||
return False
|
||||
if rate <= signal_candle["up_line"]:
|
||||
return False
|
||||
|
||||
# 防滑点追高:实时下单价 (rate) 较【信号发生时】的收盘价不能偏离过高
|
||||
# 注意:此处允许 1% 的追高滑点,如果在 15m 级别觉得 1% 的滑点成本太大,可以改为 1.005 (0.5%)
|
||||
if rate > signal_candle["close"] * 1.01:
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user