Files
Chan/strategies/ChanLun_EMA52.py
T
PorterandCursor 2c1232555e refactor: 缠论引擎迁入 chan/ 分层解耦,指标外置
将核心结构、指标与分析拆到 chan/{core,indicators,analysis,pipeline};
根目录保留兼容 shim;strategies 改为从 chan 包导入;买卖点经 bsp_macd 与 MACD 接合。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:47:13 +08:00

276 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# --- 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 chan.pipeline.ChanLun import ChanLun
from chan.core.ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX
# --------------------------------
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 freqtrade.persistence import Trade, Order
from typing import Optional
import logging
logger = logging.getLogger(__name__)
"""
大周期:1h
小周期:15m30m
大周期EMA156以下找做空机会
找到最近的中枢,中枢下跌以后穿过EMA156,EMA52均线,形成死叉,macd黄白线穿越0轴
EMA24EMA52EMA104EMA156成下跌趋势依次排列(EMA156 > EMA104 > EMA52 > EMA24
做空
1. 做空开始点位条件:
确定下跌周期,价格在大于大周期的时间周期找到MACD归零轴+EMA52阻力线,按照K线动能理论,小周期确认是否背驰,背驰则开仓并且MACD穿零轴
止损放到最近的顶分型高点或者价格突破EMA156
2. 开始点位止盈策略
计算盈亏比方式:至少1:2,到达1:2后平仓一半,移动止损到开仓价,1:3再平仓剩下的一半仓位,依次类推
如果大周期遇到底背离可以平完所有仓位
3. 加仓点位
小周期顶分型+价格接近或突破大周期EMA24但是不突破EMA52后下跌可以加仓到最大仓位+大周期黄白线归零轴/小周期顶分型+小周期EMA52归零轴
大周期顶分型+大周期macd归零轴可以加仓到最大仓位
大周期顶分型或顶分型后,macd穿零轴后价格和macd红绿柱背驰可以加仓到最大仓位
小周期顶分型+大周期macd归零轴
"""
### Now you can use logger.info('asfd') to log
# freqtrade plot-dataframe --strategy ChanLun_BTC --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309-
# freqtrade trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20260101-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1m 1h 1d 1w 1M --pairs BTC/USDT:USDT --timerange=20240101-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA52.json -e 200 --timerange=20250201-20250901
# freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20250721-
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_EMA52.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies
class ChanLun_EMA52(IStrategy):
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.15,
"360": 0.2,
"640": 0.1,
"1200": 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来控制
use_custom_stoploss = False # 启用自定义止损
trailing_stop = False
trailing_stop_positive = 0.03
trailing_stop_positive_offset = 0.06
trailing_only_offset_is_reached = False
# 关闭分批止盈/仓位调整
position_adjustment_enable = False
# startup_candle_count = 1600
big_tf = '1h'
small_tf = '15m'
last_time = None
chan = ChanLun()
last_order = None
last_trade = None
pair = 'BTC/USDT:USDT'
long_tf = '1h'
short_tf = '15m'
long_time = 60
short_time = 15
def informative_pairs(self):
return [(self.pair, "1h"),
(self.pair, "1d"),
#(self.pair, "1M"),
(self.pair, "15m"),
#(self.pair, "1w"),
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self.add_indicators(dataframe)
long_df = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
long_df = self.add_indicators(long_df)
dataframe['rsi'] = ta.RSI(long_df, timeperiod=14)
if self.last_time is None or self.last_time + timedelta(seconds=10) < datetime.now():
self.last_time = datetime.now()
logger.info("init_dataframes----------------------------")
last_price = dataframe.iloc[-1]['close']
date = dataframe.iloc[-1]['date']
tf_ema52_list = self.chan.check_price_ema52(last_price)
self.init_dataframes(dataframe)
logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list))
#print(long_df.iloc[-1])
dataframe = resampled_merge(dataframe, long_df)
return dataframe
def ema_dir(self, dataframe):
"""
趋势方向综合判断,分为三个维度:
1. ema_dir: 主趋势方向 (基于MACD零轴 + 价格与EMA52/EMA156关系)
- 3: 强多(价格在EMA156上方 + MACD在零轴上方 + 价格在EMA24上方)
- 2: 中多(价格在EMA156上方 + MACD在零轴上方)
- 1: 弱多(价格在EMA52上方 或 MACD在零轴上方,满足其一)
- -1: 弱空(价格在EMA52下方 或 MACD在零轴下方,满足其一)
- -2: 中空(价格在EMA156下方 + MACD在零轴下方)
- -3: 强空(价格在EMA156下方 + MACD在零轴下方 + 价格在EMA24下方)
- 0: 盘整(无明确方向)
2. ema_align: EMA排列状态(辅助确认趋势强度)
- 1: 多头排列 (EMA24 > EMA52 > EMA104 > EMA156)
- -1: 空头排列 (EMA24 < EMA52 < EMA104 < EMA156)
- 0: 交叉/纠缠
3. ema_slope: EMA52斜率方向(趋势加速/减速判断)
- 正值: EMA52向上倾斜,趋势加速
- 负值: EMA52向下倾斜,趋势减速
"""
close = dataframe['close']
ema24 = dataframe['ema24']
ema52 = dataframe['ema52']
ema104 = dataframe['ema104']
ema156 = dataframe['ema156']
macd_signal = dataframe['macdsignal'] # 黄线(慢线),用于判断零轴
# === 1. 主趋势方向 ===
# 核心条件:价格与EMA156的关系(大趋势)+ MACD黄线与零轴的关系
above_ema156 = close > ema156
below_ema156 = close < ema156
above_ema52 = close > ema52
below_ema52 = close < ema52
above_ema24 = close > ema24
below_ema24 = close < ema24
macd_above_zero = macd_signal > 0
macd_below_zero = macd_signal < 0
dataframe['ema_dir'] = 0
# 强多:价格在EMA156上方 + MACD零轴上方 + 价格在EMA24上方(超强势结构)
dataframe.loc[above_ema156 & macd_above_zero & above_ema24, 'ema_dir'] = 3
# 中多:价格在EMA156上方 + MACD零轴上方
dataframe.loc[above_ema156 & macd_above_zero & ~above_ema24, 'ema_dir'] = 2
# 弱多:满足其一(价格在EMA52上方 或 MACD零轴上方)
dataframe.loc[(above_ema52 & ~macd_above_zero) | (macd_above_zero & ~above_ema156), 'ema_dir'] = 1
# 弱空:满足其一(价格在EMA52下方 或 MACD零轴下方)
dataframe.loc[(below_ema52 & ~macd_below_zero) | (macd_below_zero & ~below_ema156), 'ema_dir'] = -1
# 中空:价格在EMA156下方 + MACD零轴下方
dataframe.loc[below_ema156 & macd_below_zero & ~below_ema24, 'ema_dir'] = -2
# 强空:价格在EMA156下方 + MACD零轴下方 + 价格在EMA24下方(超强空势结构)
dataframe.loc[below_ema156 & macd_below_zero & below_ema24, 'ema_dir'] = -3
# === 2. EMA排列状态(辅助参考)===
bull_align = (ema24 > ema52) & (ema52 > ema104) & (ema104 > ema156)
bear_align = (ema24 < ema52) & (ema52 < ema104) & (ema104 < ema156)
dataframe['ema_align'] = 0
dataframe.loc[bull_align, 'ema_align'] = 1
dataframe.loc[bear_align, 'ema_align'] = -1
# === 3. EMA52斜率(趋势加速/减速)===
# 用EMA52的变化率判断趋势是否在加速
dataframe['ema_slope'] = (ema52 - ema52.shift(3)) / ema52.shift(3) * 100
return dataframe
def add_indicators(self, dataframe):
dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24)
dataframe['dir24'] = dataframe['close'] - dataframe['ema24']
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
dataframe['dir52'] = dataframe['close'] - dataframe['ema52']
dataframe['ema104'] = ta.EMA(dataframe, timeperiod=104)
dataframe['dir104'] = dataframe['close'] - dataframe['ema104']
dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156)
dataframe['dir156'] = dataframe['close'] - dataframe['ema156']
dataframe['dir52_156'] = dataframe['dir52'] - dataframe['dir156']
dataframe_macd = ta.MACD(dataframe, fast=12, slow=26, signal=9)
dataframe['macdsignal'] = dataframe_macd['macdsignal']
dataframe['macd'] = dataframe_macd['macd']
dataframe['macdhist'] = dataframe_macd['macdhist']
return dataframe
def init_dataframes(self, dataframe_1m):
dataframe_15m = self.dp.get_pair_dataframe(pair=self.pair, timeframe='15m')
dataframe_1h = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d')
#dataframe_1w = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1w')
#dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M')
self.chan.init_dataframes(dataframe_1m, dataframe_15m,dataframe_1h, dataframe_1d)
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
new_entryprice = proposed_rate
if trade:
if trade.is_short:
new_entryprice = proposed_rate - 50
else:
new_entryprice = proposed_rate + 50
return new_entryprice
def custom_exit_price(self, pair: str, trade: Trade,
current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: str | None, **kwargs) -> float:
new_exitprice = proposed_rate
if trade:
if trade.is_short:
new_exitprice = proposed_rate + 50
else:
new_exitprice = proposed_rate - 50
return new_exitprice
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
# 关闭分批止盈,始终不调整仓位
return None
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
# 不做分批止盈/最终止盈处理,退出由策略信号/ROI/止损决定
return None
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['dir156'] > 0) &
(dataframe['dir52_156'] > 0) &
(dataframe['macdhist'] > 0),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['dir156'] < 0) &
(dataframe['dir52_156'] < 0) &
(dataframe['macdhist'] < 0),
'exit_long'] = 1
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