147 lines
6.3 KiB
Python
147 lines
6.3 KiB
Python
# --- Do not remove these libs ---
|
|
from statistics import median
|
|
from freqtrade.strategy import IStrategy
|
|
from technical.util import resample_to_interval, resampled_merge
|
|
from pandas import DataFrame
|
|
import talib.abstract as ta
|
|
from technical import qtpylib
|
|
|
|
### Now you can use logger.info('asfd') to log
|
|
# freqtrade plot-dataframe --strategy HeikinAshi_BTC --datadir user_data/data/binance -c ./user_data/Chan/HeikinAshi_BTC.json --timerange=20250309-
|
|
# freqtrade backtesting -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange=20251008-
|
|
# freqtrade download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
|
|
# freqtrade download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
|
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/HeikinAshi_BTC.json -e 200 --timerange=20250201-20250901
|
|
# freqtrade edge -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
|
# freqtrade plot-dataframe -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
|
|
|
# sudo docker compose run --rm heikinashi_btc backtesting -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250721-
|
|
# sudo docker compose run --rm heikinashi_btc download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
|
|
# sudo docker compose run --rm heikinashi_btc trade -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies
|
|
|
|
class HeikinAshi_BTC(IStrategy):
|
|
"""
|
|
使用 Heikin Ashi 蜡烛的双均线趋势策略,支持多/空。
|
|
|
|
逻辑摘要:
|
|
- 指标:ha_open/ha_close/ha_high/ha_low + ha_close 的快/慢 EMA
|
|
- 做多:ha_close > ha_open 且 ha_ema_fast > ha_ema_slow
|
|
- 做空:ha_close < ha_open 且 ha_ema_fast < ha_ema_slow
|
|
- 退出:相反信号或均线反转
|
|
"""
|
|
|
|
INTERFACE_VERSION: int = 3
|
|
time1h = 1440
|
|
can_short: bool = True
|
|
timeframe: str = "1h"
|
|
process_only_new_candles: bool = True
|
|
|
|
# ROI 与止损可根据需要在配置中覆盖
|
|
minimal_roi = {
|
|
"120": 0.01,
|
|
"60": 0.02,
|
|
"0": 0.03,
|
|
}
|
|
|
|
stoploss: float = -0.30
|
|
|
|
use_exit_signal: bool = True
|
|
exit_profit_only: bool = False
|
|
ignore_roi_if_entry_signal: bool = False
|
|
|
|
# 需要的历史K线数量(包含EMA等指标预热)
|
|
startup_candle_count: int = 200
|
|
|
|
plot_config = {
|
|
"main_plot": {
|
|
"ha_close": {"color": "orange"},
|
|
"ha_ema_fast": {"color": "green"},
|
|
"ha_ema_slow": {"color": "red"},
|
|
},
|
|
"subplots": {},
|
|
}
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""计算 Heikin Ashi 相关指标与均线。"""
|
|
if dataframe is None or dataframe.empty:
|
|
return dataframe
|
|
dataframe_1h = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time1h)
|
|
ha = qtpylib.heikinashi(dataframe_1h)
|
|
dataframe_1h["ha_open"] = ha["open"]
|
|
dataframe_1h["ha_close"] = ha["close"]
|
|
dataframe_1h["ha_high"] = ha["high"]
|
|
dataframe_1h["ha_low"] = ha["low"]
|
|
|
|
# 对 Heikin Ashi close 做 EMA 平滑,减少噪声
|
|
dataframe_1h["ha_ema_fast"] = ta.EMA(dataframe_1h["ha_close"], timeperiod=24)
|
|
dataframe_1h["ha_ema_slow"] = ta.EMA(dataframe_1h["ha_close"], timeperiod=52)
|
|
dataframe = resampled_merge(dataframe, dataframe_1h)
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""根据 HA 趋势定义进场信号(多/空)。"""
|
|
if dataframe is None or dataframe.empty:
|
|
return dataframe
|
|
ha_close = 'resample_{}_ha_close'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_open = 'resample_{}_ha_open'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_ema_fast = 'resample_{}_ha_ema_fast'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_ema_slow = 'resample_{}_ha_ema_slow'.format(self.get_ticker_indicator()*self.time1h)
|
|
volume = 'resample_{}_volume'.format(self.get_ticker_indicator()*self.time1h)
|
|
# 做多:HA 实体向上 且 快线上穿慢线(金叉)
|
|
dataframe.loc[
|
|
(
|
|
(dataframe[ha_close] > dataframe[ha_open]) &
|
|
(qtpylib.crossed_above(dataframe[ha_ema_fast], dataframe[ha_ema_slow])) &
|
|
(dataframe["volume"] > 0)
|
|
),
|
|
["enter_long", "enter_tag"],
|
|
] = (1, "ha_trend_long")
|
|
|
|
# 做空:HA 实体向下 且 快线下穿慢线(死叉)
|
|
dataframe.loc[
|
|
(
|
|
(dataframe[ha_close] < dataframe[ha_open]) &
|
|
(qtpylib.crossed_below(dataframe[ha_ema_fast], dataframe[ha_ema_slow])) &
|
|
(dataframe["volume"] > 0)
|
|
),
|
|
["enter_short", "enter_tag"],
|
|
] = (1, "ha_trend_short")
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""根据 HA 反转或均线反转定义出场信号(多/空)。"""
|
|
if dataframe is None or dataframe.empty:
|
|
return dataframe
|
|
ha_close = 'resample_{}_ha_close'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_open = 'resample_{}_ha_open'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_ema_fast = 'resample_{}_ha_ema_fast'.format(self.get_ticker_indicator()*self.time1h)
|
|
ha_ema_slow = 'resample_{}_ha_ema_slow'.format(self.get_ticker_indicator()*self.time1h)
|
|
volume = 'resample_{}_volume'.format(self.get_ticker_indicator()*self.time1h)
|
|
# 多单退出:HA 变为阴 或 快线下穿慢线(死叉)
|
|
dataframe.loc[
|
|
(
|
|
(
|
|
(dataframe[ha_close] < dataframe[ha_open]) |
|
|
(qtpylib.crossed_below(dataframe[ha_ema_fast], dataframe[ha_ema_slow]))
|
|
)
|
|
& (dataframe["volume"] > 0)
|
|
),
|
|
["exit_long", "exit_tag"],
|
|
] = (1, "ha_long_exit")
|
|
|
|
# 空单退出:HA 变为阳 或 快线上穿慢线(金叉)
|
|
dataframe.loc[
|
|
(
|
|
(
|
|
(dataframe[ha_close] > dataframe[ha_open]) |
|
|
(qtpylib.crossed_above(dataframe[ha_ema_fast], dataframe[ha_ema_slow]))
|
|
)
|
|
& (dataframe["volume"] > 0)
|
|
),
|
|
["exit_short", "exit_tag"],
|
|
] = (1, "ha_short_exit")
|
|
|
|
return dataframe
|
|
def get_ticker_indicator(self):
|
|
return int(self.timeframe[:-1]) |