From 13c226ee6cf03caf154465b176bc1a346a21f59d Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Wed, 11 Feb 2026 19:31:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9D=87=E7=BA=BF=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/ChanLun_EMA_Align.json | 83 +++++++++++++ strategies/ChanLun_EMA_Align.py | 200 ++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 config/ChanLun_EMA_Align.json create mode 100644 strategies/ChanLun_EMA_Align.py diff --git a/config/ChanLun_EMA_Align.json b/config/ChanLun_EMA_Align.json new file mode 100644 index 0000000..db786c1 --- /dev/null +++ b/config/ChanLun_EMA_Align.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "exit_timeout_count": 5, + "unit": "minutes" + }, + "entry_pricing": { + "price_side": "same", + "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 + }, + "exchange": { + "name": "binance", + "key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT" + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": true, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "0.0.0.0", + "listen_port": 8820, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/strategies/ChanLun_EMA_Align.py b/strategies/ChanLun_EMA_Align.py new file mode 100644 index 0000000..d5ef098 --- /dev/null +++ b/strategies/ChanLun_EMA_Align.py @@ -0,0 +1,200 @@ +# --- 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 +# -------------------------------- +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 +小周期:15m,30m +大周期EMA156以下找做空机会 +找到最近的中枢,中枢下跌以后穿过EMA156,EMA52均线,形成死叉,macd黄白线穿越0轴 +EMA24,EMA52,EMA104,EMA156成下跌趋势依次排列(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 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 + } + + 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 = 15 + time15 = 15 + time30 = 30 + time60 = 60 + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe = self.add_indicators(dataframe) + dataframe_5m = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time5) + dataframe_5m = self.add_indicators(dataframe_5m) + dataframe = resampled_merge(dataframe, dataframe_5m) + 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['dir52_104'] = dataframe['dir52'] - dataframe['dir104'] + 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_align'] = ( + ((dataframe['ema24'] > dataframe['ema52']) & (dataframe['ema52'] > dataframe['ema104'])) | + ((dataframe['ema24'] < dataframe['ema52']) & (dataframe['ema52'] < dataframe['ema104'])) + ) + 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_5m_align = 'resample_{}_ema_align'.format(self.get_ticker_indicator() * self.time5) + # 使用高周期的 dir52_156 方向作为多空判定依据 + resample_5m_dir = 'resample_{}_dir52_104'.format(self.get_ticker_indicator() * self.time5) + resample_5m_signal = 'resample_{}_macdsignal'.format(self.get_ticker_indicator() * self.time5) + dataframe.loc[ + (dataframe[resample_5m_align]) & + (dataframe[resample_5m_dir] < 0) & + (dataframe[resample_5m_signal] > 0), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + (dataframe[resample_5m_align]) & + (dataframe[resample_5m_dir] > 0) & + (dataframe[resample_5m_signal] < 0), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + 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', 'exit_tag']] = (1, 'long_exit_signal_chan') + dataframe.loc[ + (dataframe['macd'] > 0) & + (dataframe['dir156'] > 0) & + (dataframe['dir52_156'] > 0) & + (dataframe['macdhist'] > 0), + ['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]) \ No newline at end of file