Files
Chan/strategies/ChanLun_EMA_Align.py
jackyu66gitandCursor 74dec4e50b refactor: 缠论引擎包化与 Web 分层(ECR-001)
将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务;
前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 18:48:20 +08:00

194 lines
9.1 KiB
Python
Raw Permalink 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 chanlun import ChanLun
from chanlun.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_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange=20260101-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json -t 1m 1m 5m 15m 30m 1h 1d 1w 1M --pairs BTC/USDT:USDT --timerange=20240101-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA_Align.json -e 200 --timerange=20250201-20250901
# freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange=20250721-
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies
class ChanLun_EMA_Align(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
}
startup_candle_count = 1600
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
time5 = 5
time15 = 15
time30 = 30
time60 = 60
chan = ChanLun()
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self.add_indicators(dataframe)
dataframe_30m = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time30)
dataframe_30m = self.add_indicators(dataframe_30m)
dataframe_30m['ema_state'] = self.chan.get_ema_state(dataframe_30m)
dataframe_30m['state'] = self.chan.get_klu_state(dataframe_30m)
#print(dataframe_5m.iloc[-1])
dataframe = resampled_merge(dataframe, dataframe_30m)
return dataframe
def add_indicators(self, dataframe):
dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24)
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
dataframe['ema104'] = ta.EMA(dataframe, timeperiod=104)
dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156)
dataframe['ema52_price'] = dataframe['close'] - dataframe['ema52']
dataframe['ema156_price'] = dataframe['close'] - dataframe['ema156']
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']
dataframe['ema_dir'] = dataframe['ema52'] - dataframe['ema156']
return dataframe
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:
resample_30m_dir = 'resample_{}_ema_dir'.format(self.get_ticker_indicator() * self.time30)
resample_30m_price = 'resample_{}_ema52_price'.format(self.get_ticker_indicator() * self.time30)
resample_30m_signal = 'resample_{}_macdsignal'.format(self.get_ticker_indicator() * self.time30)
resample_30m_state = 'resample_{}_state'.format(self.get_ticker_indicator() * self.time30)
resample_30m_ema_state = 'resample_{}_ema_state'.format(self.get_ticker_indicator() * self.time30)
dataframe.loc[
(dataframe[resample_30m_dir].shift(self.time30*2) > 0) &
(dataframe[resample_30m_ema_state].shift(self.time30) == "2") &
(dataframe[resample_30m_ema_state].shift(self.time30*2) == "1"),
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
dataframe.loc[
(dataframe[resample_30m_dir].shift(self.time30*2) < 0) &
(dataframe[resample_30m_ema_state].shift(self.time30) == "-2") &
(dataframe[resample_30m_ema_state].shift(self.time30*2) == "-1"),
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
state = 'resample_{}_state'.format(self.get_ticker_indicator() * self.time30)
dataframe.loc[
(dataframe[state].shift(self.time30) == "10"),
['exit_long', 'exit_tag']] = (1, 'long_exit_signal_chan')
dataframe.loc[
(dataframe[state].shift(self.time30) == "-10"),
['exit_short', 'exit_tag']] = (1, 'short_exit_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])