添加三类买卖点识别和图上标注,还不错,感觉可以赚钱了,明天继续优化一下
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
# --- Do not remove these libs ---
|
||||
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
|
||||
# --------------------------------
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from datetime import datetime, timedelta
|
||||
from freqtrade.persistence import Trade, Order
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# freqtrade trade -c ./user_data/Chan/config/BTC_Perpetual_Futures.json --strategy BTC_Perpetual_Futures --strategy-path ./user_data/Chan/strategies
|
||||
# freqtrade backtesting -c ./user_data/Chan/config/BTC_Perpetual_Futures.json --strategy BTC_Perpetual_Futures --strategy-path ./user_data/Chan/strategies --timerange=20260101-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/BTC_Perpetual_Futures.json -t 1m --pairs BTC/USDT:USDT --timerange=20260101-
|
||||
|
||||
|
||||
class BTC_Perpetual_Futures(IStrategy):
|
||||
"""
|
||||
BTC永续合约交易策略 - 优化版
|
||||
- 基于缠论(ChanLun)技术分析 + RSI/MACD/布林带/ATR 多指标共振
|
||||
- 支持做多和做空
|
||||
- 基于ATR的动态止损
|
||||
- 成交量确认过滤
|
||||
"""
|
||||
INTERFACE_VERSION: int = 3
|
||||
|
||||
# ROI配��� - 分阶段止盈
|
||||
minimal_roi = {
|
||||
"0": 0.08, # 立即: 8%止盈
|
||||
"60": 0.05, # 1小时后: 5%止盈
|
||||
"180": 0.02, # 3小时后: 2%止盈
|
||||
"360": 0 # 6小时后: 保本出场
|
||||
}
|
||||
|
||||
can_short = True
|
||||
lev = 1.0 # 杠杆倍数,建议新手用1-3倍
|
||||
|
||||
stoploss = -0.04 # 默认4%止损(custom_stoploss会覆盖)
|
||||
use_custom_stoploss = True
|
||||
|
||||
trailing_stop = False
|
||||
position_adjustment_enable = False
|
||||
startup_candle_count = 1440 # 需要1440根1分钟K线预热
|
||||
|
||||
# 时间框架常量
|
||||
time5 = 5
|
||||
time15 = 15
|
||||
time30 = 30
|
||||
time60 = 60
|
||||
|
||||
chan = ChanLun()
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""添加多时间框架技术指标"""
|
||||
# 重采样到5分钟和30分钟
|
||||
dataframe_5m = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time5)
|
||||
dataframe_30m = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time30)
|
||||
|
||||
# 添加技术指标
|
||||
dataframe = self._add_indicators(dataframe)
|
||||
dataframe_5m = self._add_indicators(dataframe_5m)
|
||||
dataframe_30m = self._add_indicators(dataframe_30m)
|
||||
|
||||
# 缠论状态分析
|
||||
dataframe_5m['state'] = self.chan.get_klu_state(dataframe_5m)
|
||||
dataframe_30m['state'] = self.chan.get_klu_state(dataframe_30m)
|
||||
|
||||
# 合并多时间框架数据
|
||||
dataframe = resampled_merge(dataframe, dataframe_5m)
|
||||
dataframe = resampled_merge(dataframe, dataframe_30m)
|
||||
|
||||
return dataframe
|
||||
|
||||
def _add_indicators(self, df: DataFrame) -> DataFrame:
|
||||
"""添加技术指标"""
|
||||
# MACD
|
||||
macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
df['macd'] = macd['macd']
|
||||
df['macdsignal'] = macd['macdsignal']
|
||||
df['macdhist'] = macd['macdhist']
|
||||
|
||||
# 布林带 (20周期, 2倍标准差)
|
||||
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
|
||||
df['bb_upper'] = bb['upperband']
|
||||
df['bb_middle'] = bb['middleband']
|
||||
df['bb_lower'] = bb['lowerband']
|
||||
|
||||
# ATR - 用于动态止损
|
||||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||||
|
||||
# EMA均线系统
|
||||
df['ema5'] = ta.EMA(df, timeperiod=5)
|
||||
df['ema10'] = ta.EMA(df, timeperiod=10)
|
||||
df['ema26'] = ta.EMA(df, timeperiod=26)
|
||||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||||
|
||||
# RSI
|
||||
df['rsi'] = ta.RSI(df, timeperiod=14)
|
||||
|
||||
# 成交量比(当前成交量 / 10周期均量)
|
||||
avg_vol = df['volume'].rolling(window=10).mean()
|
||||
df['volume_ratio'] = (df['volume'] / avg_vol).fillna(1.0)
|
||||
|
||||
return df
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
进场信号定义 - 优化版
|
||||
- 做多: 缠论底部信号(-10/-20) + RSI<65 + 放量 + EMA确认
|
||||
- 做空: 缠论顶部信号(10/20) + RSI>35 + 放量 + EMA确认
|
||||
"""
|
||||
state_30m = 'resample_{}_state'.format(self.get_ticker_indicator() * self.time30)
|
||||
ema52_30m = 'resample_{}_ema52'.format(self.get_ticker_indicator() * self.time30)
|
||||
close_30m = 'resample_{}_close'.format(self.get_ticker_indicator() * self.time30)
|
||||
shift = self.time30
|
||||
|
||||
# 做多信号:只在缠论-10信号 + 强势过滤
|
||||
dataframe.loc[
|
||||
(dataframe[state_30m].shift(shift) == "-10") &
|
||||
(dataframe['rsi'] < 65) &
|
||||
(dataframe['rsi'] > 20) & # RSI不过冷
|
||||
(dataframe['volume_ratio'] > 1.2) & # 放量确认
|
||||
(dataframe['close'] > dataframe['ema52']) & # 价格在EMA52上方
|
||||
(dataframe['macd'] > dataframe['macdsignal']), # MACD金叉
|
||||
['enter_long', 'enter_tag']] = (1, 'chan_long')
|
||||
|
||||
# 做空信号:只在缠论10信号 + 强势过滤
|
||||
dataframe.loc[
|
||||
(dataframe[state_30m].shift(shift) == "10") &
|
||||
(dataframe['rsi'] > 35) &
|
||||
(dataframe['rsi'] < 80) & # RSI不过热
|
||||
(dataframe['volume_ratio'] > 1.2) & # 放量确认
|
||||
(dataframe['close'] < dataframe['ema52']) & # 价格在EMA52下方
|
||||
(dataframe['macd'] < dataframe['macdsignal']), # MACD死叉
|
||||
['enter_short', 'enter_tag']] = (1, 'chan_short')
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
出场信号定义
|
||||
- 做多出场: 缠论顶部反转信号
|
||||
- 做空出场: 缠论底部反转信号
|
||||
"""
|
||||
state_30m = 'resample_{}_state'.format(self.get_ticker_indicator() * self.time30)
|
||||
shift = self.time30
|
||||
|
||||
dataframe.loc[
|
||||
(dataframe[state_30m].shift(shift) == "10"),
|
||||
['exit_long', 'exit_tag']] = (1, 'chan_exit_long')
|
||||
|
||||
dataframe.loc[
|
||||
(dataframe[state_30m].shift(shift) == "-10"),
|
||||
['exit_short', 'exit_tag']] = (1, 'chan_exit_short')
|
||||
|
||||
return dataframe
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float, after_fill: bool,
|
||||
**kwargs) -> float | None:
|
||||
"""
|
||||
基于ATR的动态止损
|
||||
止损距离 = 开仓价 ± 1.5*ATR
|
||||
"""
|
||||
try:
|
||||
entry_atr = trade.get_custom_data(key="entry_atr")
|
||||
if entry_atr is None:
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
||||
if dataframe is not None and len(dataframe) > 0 and 'atr' in dataframe.columns:
|
||||
entry_atr = float(dataframe.iloc[-1]['atr'])
|
||||
else:
|
||||
return -0.04
|
||||
|
||||
if trade.is_short:
|
||||
stop_price = trade.open_rate + (float(entry_atr) * 1.5)
|
||||
else:
|
||||
stop_price = trade.open_rate - (float(entry_atr) * 1.5)
|
||||
|
||||
return stoploss_from_absolute(stop_price, current_rate, is_short=trade.is_short)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"custom_stoploss error: {e}")
|
||||
return None
|
||||
|
||||
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, **kwargs):
|
||||
"""快速止盈:浮盈超过0.5%直接出场"""
|
||||
if current_profit > 0.005:
|
||||
return "quick_profit"
|
||||
return None
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime, entry_tag: str | None,
|
||||
side: str, **kwargs) -> bool:
|
||||
"""进场前最终过滤:ATR太小或RSI极端时拒绝"""
|
||||
try:
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
if dataframe is None or len(dataframe) == 0:
|
||||
return False
|
||||
|
||||
last = dataframe.iloc[-1]
|
||||
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator() * self.time30)
|
||||
atr_val = float(last.get(atr_str, 0) or 0)
|
||||
if atr_val < 0.001:
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"confirm_trade_entry error: {e}")
|
||||
return True
|
||||
|
||||
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
|
||||
"""订单成交时保存ATR用于止损计算"""
|
||||
try:
|
||||
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
||||
if dataframe is not None and len(dataframe) > 0:
|
||||
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator() * self.time30)
|
||||
last = dataframe.iloc[-1]
|
||||
entry_atr = float(last.get(atr_str, 0) or 0) * 3
|
||||
trade.set_custom_data(key="entry_atr", value=entry_atr)
|
||||
except Exception as e:
|
||||
logger.warning(f"order_filled error: {e}")
|
||||
|
||||
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||
"""入场价微调,减少滑点"""
|
||||
if trade:
|
||||
if trade.is_short:
|
||||
return proposed_rate - 50
|
||||
else:
|
||||
return proposed_rate + 50
|
||||
return proposed_rate
|
||||
|
||||
def custom_exit_price(self, pair: str, trade: Trade,
|
||||
current_time: datetime, proposed_rate: float,
|
||||
current_profit: float, exit_tag: str | None, **kwargs) -> float:
|
||||
"""出场价微调,减少滑点"""
|
||||
if trade:
|
||||
if trade.is_short:
|
||||
return proposed_rate + 50
|
||||
else:
|
||||
return proposed_rate - 50
|
||||
return proposed_rate
|
||||
|
||||
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) -> int:
|
||||
return int(self.timeframe[:-1])
|
||||
@@ -349,12 +349,16 @@ class ChanLun_BTC_30(IStrategy):
|
||||
state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30)
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "10")
|
||||
(dataframe[state30].shift(shift30) == "10") |
|
||||
(dataframe[state30].shift(shift30) == "20") |
|
||||
(dataframe[state30].shift(shift30) == "30")
|
||||
),
|
||||
['exit_long', 'exit_tag']] = (1, 'long_close_30')
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "-10")
|
||||
(dataframe[state30].shift(shift30) == "-10") |
|
||||
(dataframe[state30].shift(shift30) == "-20") |
|
||||
(dataframe[state30].shift(shift30) == "-30")
|
||||
),
|
||||
['exit_short', 'exit_tag']] = (1, 'short_close_30')
|
||||
return dataframe
|
||||
|
||||
Reference in New Issue
Block a user