添加策略

This commit is contained in:
2025-10-25 17:19:16 +08:00
parent 7fdac25742
commit 04ad5b6f1b
9 changed files with 549 additions and 294 deletions

183
.gitignore vendored Normal file
View File

@@ -0,0 +1,183 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Data files (keep structure but ignore large data)
data/
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Docker
.dockerignore
# Logs
*.log
logs/
# Temporary files
tmp/
temp/
*.tmp
*.temp
# API keys and secrets
.env
config.ini
secrets.json
api_keys.txt
# Database files
*.db
*.sqlite
*.sqlite3
# Backup files
*.bak
*.backup
user_data/data
user_data/freqaimodels
user_data/hyperopts
user_data/logs
user_data/backtest_results

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM freqtradeorg/freqtrade:stable
# 设置工作目录
WORKDIR /freqtrade
# 复制用户数据到镜像(策略、配置等),但排除 data 和 backtest_results 目录
COPY user_data /freqtrade/user_data
RUN rm -rf /freqtrade/user_data/data /freqtrade/user_data/backtest_results
# 暴露 freqtrade WebUI 端口
EXPOSE 8077
# 设置启动命令,与 compose 保持一致
CMD ["trade", "--config", "/freqtrade/user_data/config.json", "--strategy", "SimpleRSIStrategyFixed"]

View File

@@ -10,4 +10,4 @@ services:
command: >
trade
--config ./user_data/config.json
--strategy MACDStrategy
--strategy SimpleRSIStrategyFixed

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
@@ -41,7 +41,8 @@
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ETH/USDT"
"ETH/USDT",
"BTC/USDT"
],
"pair_blacklist": [
]

View File

@@ -42,9 +42,9 @@ class MACDStrategy(IStrategy):
INTERFACE_VERSION = 3
minimal_roi = {"0": 100}
stoploss = -1
stoploss = -0.05
trailing_stop = False
timeframe = '15m'
timeframe = '4h'
use_exit_signal = True
exit_profit_only = False
@@ -72,7 +72,7 @@ class MACDStrategy(IStrategy):
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
dataframe['cci'] = ta.CCI(dataframe, 26)
dataframe['cci'] = ta.CCI(dataframe, 14)
dataframe['TD'] = self.TD(dataframe)
return dataframe
@@ -80,15 +80,14 @@ class MACDStrategy(IStrategy):
# 入场1d与4h CCI < -100且4h CCI上升当前 > 前一根)
dataframe.loc[
(
# (dataframe['macdhist'] < 0) &
# (dataframe['macdhist'] > dataframe['macdhist'].shift(1)) &
(dataframe['macdhist'] < 0) &
(dataframe['macdhist'] > dataframe['macdhist'].shift(1)) &
# (dataframe['macdsignal'] < 0) &
# (dataframe['macd'] < dataframe['macdsignal']) &
# (dataframe['cci'] < -100) &
# (dataframe['cci'].shift(1) < dataframe['cci']) &
(dataframe['cci'] < -100) &
(dataframe['cci'] > dataframe['cci'].shift(1))
# (dataframe['macdhist'] < 0) &
(dataframe['TD'] == 1) &
(dataframe['volume'] > 0)
# (dataframe['volume'] > 0)
),
'enter_long',
] = 1
@@ -99,15 +98,17 @@ class MACDStrategy(IStrategy):
# 离场1d与4h CCI > 100且4h CCI下降当前 < 前一根)
dataframe.loc[
(
# (dataframe['macdhist'] > 0) &
((dataframe['macdhist'] < 0) &
(dataframe['macdhist'].shift(1) > 0) )
| (qtpylib.crossed_below(dataframe['macd'], dataframe['macdsignal']) )
# (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
# (dataframe['macdsignal'] > 0) &
# (dataframe['macd'] > dataframe['macdsignal']) &
# (dataframe['cci'] > 100 )&
# (dataframe['cci'].shift(1) > dataframe['cci']) &
# (dataframe['cci'] < dataframe['cci'].shift(1)) &
# (dataframe['macdhist'] > 0) &
(dataframe['TD'] == -1) &
(dataframe['volume'] > 0)
# (dataframe['TD'] == -1) &
# (dataframe['volume'] > 0)
),
'exit_long',
] = 1

View File

@@ -0,0 +1,66 @@
import talib.abstract as ta
import pandas as pd
import numpy as np
from freqtrade.strategy import IStrategy
import logging
logger = logging.getLogger(__name__)
class SimpleRSIStrategyOptimized(IStrategy):
INTERFACE_VERSION = 3
can_short: bool = False
stoploss = -0.05
minimal_roi = {"0": 100} # 由止盈逻辑替代
timeframe = '1d'
# === 策略参数(可优化)===
ema_short = 10
ema_long = 20
rsi_period = 6
rsi_oversold = 20
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
# EMA
dataframe['ema_short'] = ta.EMA(dataframe['close'], timeperiod=self.ema_short)
dataframe['ema_long'] = ta.EMA(dataframe['close'], timeperiod=self.ema_long)
# RSI
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=self.rsi_period)
# 辅助昨日值shift 1
dataframe['ema_short_prev'] = dataframe['ema_short'].shift(1)
dataframe['rsi_prev'] = dataframe['rsi'].shift(1)
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(
# 趋势条件EMA 短期 > 长期 且 短期 EMA 上升
(dataframe['ema_short'] > dataframe['ema_long']) &
(dataframe['ema_short'] > dataframe['ema_short_prev']) &
# RSI 超卖后反弹
(dataframe['rsi_prev'] <= self.rsi_oversold) &
(dataframe['rsi'] > dataframe['rsi_prev'])
),
'enter_long',
] = 1
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(
# 趋势破坏EMA 死叉
(dataframe['ema_short'] < dataframe['ema_long']) |
# 或价格跌破长期 EMA支撑失效
(dataframe['close'] < dataframe['ema_long'])
),
'exit_long',
] = 1
return dataframe

View File

@@ -118,8 +118,8 @@ class SimpleRSIStrategyFixed(IStrategy):
f"方向: {side}, "
f"仓位: {position}"
)
dingtalk.send_text(
content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}")
# dingtalk.send_text(
# content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}")
return None

View File

@@ -2,6 +2,7 @@
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
from dingtalk import DingTalkBot
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
@@ -37,12 +38,22 @@ import talib.abstract as ta
from technical import qtpylib
webhook = "https://oapi.dingtalk.com/robot/send?access_token=21de667159edadd33172c6ec414a2addf9c6359189350ffd36819d2a20e8a0f4" # 填写你的webhook
secret = "SEC43a0fa0b29717f98637a119b92a0bd5f7b2b6da671bdd2bd1279ed8323454d5e" # 填写你的加签token如果有否则留空
# CTA 群机器人
# webhook = "https://oapi.dingtalk.com/robot/send?access_token=87c7abfcdd69b699c32da4e4f5981cd2ca6b0445474fc6ffb36f2ed0f6262fbb"
# secret = "SECf3d6b43f2f8a87ab91feffd052e71ec314fbf57a1842e483fe07af3c0a0e5aa6"
dingtalk = DingTalkBot(webhook, secret)
class CCIMultiTimeframeSpotStrategy(IStrategy):
# 策略参数
INTERFACE_VERSION = 3
minimal_roi = {"0": 100}
stoploss = -1
use_custom_stoploss = False
trailing_stop = False
timeframe = '4h'
@@ -50,6 +61,37 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
exit_profit_only = False
ignore_roi_if_entry_signal = False
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
# 交易对
trading_pair = pair
# 时间
fill_time = order.order_filled_date or current_time
# 价格
fill_price = order.average or order.price
# 买入还是卖出
side = order.ft_order_side # 'buy' 或 'sell'
# 仓位(当前持仓数量)
position = trade.amount
# 或者使用日志
logger.info(
f"订单成交 - 交易对: {trading_pair}, "
f"时间: {fill_time}, "
f"价格: {fill_price}, "
f"方向: {side}, "
f"仓位: {position}"
)
# if self.config["runmode"].value in ("live", "dry_run"):
logger.info(f"11111111{self.config['runmode']}")
# dingtalk.send_text(
# content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}")
return None
def TD(self, dataframe:DataFrame):
close = dataframe['close'].to_list()
td = [0,0,0,0]
@@ -98,6 +140,7 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
# dataframe['adx_hist_4h'] = ta.ADX(dataframe['macdhist_4h'])
dataframe['TD'] = self.TD(dataframe)
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
logger.info(dataframe.tail())
return dataframe
@@ -132,3 +175,14 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
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:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
candle = dataframe.iloc[-1].squeeze()
side = 1 if trade.is_short else -1
return stoploss_from_absolute(current_rate + (side * candle["atr"] * 3),
current_rate=current_rate,
is_short=trade.is_short,
leverage=trade.leverage)

View File

@@ -36,23 +36,26 @@ import talib.abstract as ta
from technical import qtpylib
# This class is a sample. Feel free to customize it.
class SampleStrategy(IStrategy):
# 基于x.py策略的MA60/MA120趋势跟踪策略
class MA60MA120TrendStrategy(IStrategy):
"""
This is a sample strategy to inspire you.
More information in https://www.freqtrade.io/en/latest/strategy-customization/
基于x.py策略的MA60/MA120趋势跟踪策略
You can:
:return: a Dataframe with all mandatory indicators for the strategies
- Rename the class name (Do not forget to update class_name)
- Add any methods you want to build your strategy
- Add any lib you need to build your strategy
策略逻辑:
1. 买入条件:
- 价格在MA60上方且距离MA60不超过10%
- MA60呈上升趋势过去5天中至少3天上升
- 成交量放大当日成交量大于过去5日平均
- 前5天中下跌天数不超过2天
- 当天收盘价大于5天前的价格
- 价格在MA120上方
- MA120过去5天不是下降趋势
- RSI小于阈值默认70
You must keep:
- the lib in the section "Do not remove these libs"
- the methods: populate_indicators, populate_entry_trend, populate_exit_trend
You should keep:
- timeframe, minimal_roi, stoploss, trailing_*
2. 卖出条件:
- 价格跌破MA60且跌幅超过10%
更多信息: https://www.freqtrade.io/en/latest/strategy-customization/
"""
# Strategy interface version - allow new iterations of the strategy interface.
@@ -63,26 +66,25 @@ class SampleStrategy(IStrategy):
can_short: bool = False
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi".
minimal_roi = {
# "120": 0.0, # exit after 120 minutes at break even
"60": 0.01,
"30": 0.02,
"0": 0.04,
}
# 基于x.py策略使用更保守的ROI设置
# minimal_roi = {
# "30": 0.3, # 20% 目标收益
# # ""
# }
# Optimal stoploss designed for the strategy.
# This attribute will be overridden if the config file contains "stoploss".
stoploss = -0.10
# 基于x.py策略的卖出条件跌破MA60且跌幅超过10%
stoploss = -1
# Trailing stoploss
trailing_stop = False
# trailing_stop = True
# trailing_only_offset_is_reached = False
# trailing_stop_positive = 0.01
# trailing_stop_positive = 0.3
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Optimal timeframe for the strategy.
timeframe = "5m"
# 使用日线数据因为x.py策略基于日线
timeframe = "1d"
# Run "populate_indicators()" only for new candle.
process_only_new_candles = True
@@ -92,13 +94,15 @@ class SampleStrategy(IStrategy):
exit_profit_only = False
ignore_roi_if_entry_signal = False
# Hyperoptable parameters
buy_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True)
sell_rsi = IntParameter(low=50, high=100, default=70, space="sell", optimize=True, load=True)
short_rsi = IntParameter(low=51, high=100, default=70, space="sell", optimize=True, load=True)
exit_short_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True)
# 移除原有的RSI参数因为新策略使用固定值
# 可以添加其他可优化的参数
ma60_period = IntParameter(low=30, high=90, default=60, space="buy", optimize=True, load=True)
ma120_period = IntParameter(low=60, high=180, default=120, space="buy", optimize=True, load=True)
rsi_threshold = IntParameter(low=50, high=80, default=70, space="buy", optimize=True, load=True)
volume_surge_multiplier = DecimalParameter(low=0.5, high=2.0, default=1.0, space="buy", optimize=True, load=True)
# Number of candles the strategy requires before producing valid signals
# 需要更多数据来计算MA120和趋势指标
startup_candle_count: int = 200
# Optional order type mapping.
@@ -114,16 +118,21 @@ class SampleStrategy(IStrategy):
plot_config = {
"main_plot": {
"tema": {},
"sar": {"color": "white"},
"ma60": {"color": "blue", "width": 2},
"ma120": {"color": "orange", "width": 2},
},
"subplots": {
"MACD": {
"macd": {"color": "blue"},
"macdsignal": {"color": "orange"},
},
"RSI": {
"rsi": {"color": "red"},
"rsi_threshold": {"color": "gray", "type": "line", "width": 1},
},
"Volume": {
"volume": {"color": "lightblue"},
"volume_5ma": {"color": "blue"},
},
"Signals": {
"buy_signal": {"color": "green", "type": "scatter", "marker": "^"},
"sell_signal": {"color": "red", "type": "scatter", "marker": "v"},
},
},
}
@@ -143,284 +152,211 @@ class SampleStrategy(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
基于x.py策略的技术指标计算
实现MA60/MA120趋势跟踪策略的所有必要指标
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
# Momentum Indicators
# 基础移动平均线指标
# ------------------------------------
# ADX
dataframe["adx"] = ta.ADX(dataframe)
# MA60 - 使用可优化参数
dataframe['ma60'] = ta.SMA(dataframe, timeperiod=60)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# MA120 - 使用可优化参数
dataframe['ma120'] = ta.SMA(dataframe, timeperiod=120)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# RSI指标
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
# # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
# dataframe["kc_upperband"] = keltner["upper"]
# dataframe["kc_lowerband"] = keltner["lower"]
# dataframe["kc_middleband"] = keltner["mid"]
# dataframe["kc_percent"] = (
# (dataframe["close"] - dataframe["kc_lowerband"]) /
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"])
# )
# dataframe["kc_width"] = (
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"]) / dataframe["kc_middleband"]
# )
# # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe)
# RSI
dataframe["rsi"] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe["fastd"] = stoch_fast["fastd"]
dataframe["fastk"] = stoch_fast["fastk"]
# # Stochastic RSI
# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# MACD
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
# MFI
dataframe["mfi"] = ta.MFI(dataframe)
# # ROC
# dataframe['roc'] = ta.ROC(dataframe)
# Overlap Studies
# 成交量相关指标
# ------------------------------------
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["bb_percent"] = (dataframe["close"] - dataframe["bb_lowerband"]) / (
dataframe["bb_upperband"] - dataframe["bb_lowerband"]
# 成交量5日移动平均
dataframe['volume_5ma'] = dataframe['volume'].rolling(window=5).mean()
# 成交量放大信号当日成交量大于过去5日平均的倍数
dataframe['volume_surge'] = dataframe['volume'] > (dataframe['volume_5ma'] * 1)
# 价格与MA60关系指标
# ------------------------------------
# 价格在MA60上方且距离MA60不超过10%
dataframe['price_above_ma60'] = (
(dataframe['close'] > dataframe['ma60']) &
((dataframe['ma60'] - dataframe['close']) / dataframe['close'] < 0.1)
)
dataframe["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe[
"bb_middleband"
]
# Bollinger Bands - Weighted (EMA based instead of SMA)
# weighted_bollinger = qtpylib.weighted_bollinger_bands(
# qtpylib.typical_price(dataframe), window=20, stds=2
# )
# dataframe["wbb_upperband"] = weighted_bollinger["upper"]
# dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
# dataframe["wbb_middleband"] = weighted_bollinger["mid"]
# dataframe["wbb_percent"] = (
# (dataframe["close"] - dataframe["wbb_lowerband"]) /
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
# )
# dataframe["wbb_width"] = (
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) /
# dataframe["wbb_middleband"]
# )
# 价格在MA120上方
dataframe['price_above_ma120'] = dataframe['close'] > dataframe['ma120']
# # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# 前一天在MA60附近95%-105%
dataframe['prev_near_ma'] = (
(dataframe['close'].shift(1) >= dataframe['ma60'].shift(1) * 0.95) &
(dataframe['close'].shift(1) <= dataframe['ma60'].shift(1) * 1.05)
)
# # SMA - Simple Moving Average
# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
# 前一天在MA60下方
dataframe['prev_below_ma'] = dataframe['close'].shift(1) < dataframe['ma60'].shift(1)
# Parabolic SAR
dataframe["sar"] = ta.SAR(dataframe)
# 突破信号前一天在MA60附近或下方当天在MA60上方
dataframe['breakthrough'] = (
(dataframe['prev_below_ma'] | dataframe['prev_near_ma']) &
dataframe['price_above_ma60']
)
# TEMA - Triple Exponential Moving Average
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# MA60趋势指标
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe["htsine"] = hilbert["sine"]
dataframe["htleadsine"] = hilbert["leadsine"]
# Pattern Recognition - Bullish candlestick patterns
# 计算MA60的上升趋势过去5天中至少3天上升
trend_days = 5
ma_trend = []
for i in range(len(dataframe)):
if i < trend_days - 1:
ma_trend.append(False)
else:
start_idx = i - trend_days + 1
ma_values = dataframe['ma60'].iloc[start_idx:i+1].values
if pd.isna(ma_values).any():
ma_trend.append(False)
else:
# 检查是否呈上升趋势至少3天上升
ma_trend.append(sum(ma_values[1:] > ma_values[:-1]) > trend_days - 3)
dataframe['ma_uptrend'] = ma_trend
# MA120趋势指标
# ------------------------------------
# # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns
# 计算MA120的趋势过去5天不能为下降
ma120_trend = []
for i in range(len(dataframe)):
if i < 4:
ma120_trend.append(True) # 数据不足时不限制
else:
window = dataframe['ma120'].iloc[i-4:i+1].values
if pd.isna(window).any():
ma120_trend.append(True)
else:
# 只要有一天不是上升就不是下降趋势
ma120_trend.append((window[1:] >= window[:-1]).all())
dataframe['ma120_not_downtrend'] = ma120_trend
# 价格动量指标
# ------------------------------------
# # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns
# 阳线条件:收盘价 > 开盘价且阳线幅度至少0.5%
dataframe['is_bullish'] = (
(dataframe['close'] > dataframe['open']) &
((dataframe['close'] - dataframe['open']) / dataframe['open'] >= 0.005)
)
# 前5天的下跌天数不超过2天
dataframe['is_down_day'] = dataframe['close'] < dataframe['open']
dataframe['down_days_in_5'] = dataframe['is_down_day'].rolling(window=5, min_periods=1).sum()
dataframe['not_too_many_downs'] = dataframe['down_days_in_5'] <= 2
# 当天收盘价大于第五天前的价格
dataframe['price_higher_than_5days_ago'] = dataframe['close'] > dataframe['close'].shift(5)
# RSI条件 - 使用可优化参数
dataframe['rsi_lt_threshold'] = dataframe['rsi'] < 70
# 综合买入信号
# ------------------------------------
# # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# # Chart type
# # ------------------------------------
# # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close']
# dataframe['ha_high'] = heikinashi['high']
# dataframe['ha_low'] = heikinashi['low']
dataframe["buy_signal"] = (
dataframe["price_above_ma60"] &
dataframe["ma_uptrend"] &
dataframe['ma60'].notna() &
dataframe["volume_surge"] &
dataframe["not_too_many_downs"] &
dataframe["price_higher_than_5days_ago"] &
dataframe['price_above_ma120'] &
dataframe['ma120_not_downtrend']
# &
# dataframe['rsi_lt_threshold']
)
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
"""
# 卖出信号价格跌破MA60且跌幅超过10%
dataframe['sell_signal'] = (
(~dataframe['price_above_ma60']) &
((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
dataframe['ma60'].notna()
)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the entry signal for the given dataframe
基于x.py策略的买入信号逻辑
实现MA60/MA120趋势跟踪策略的买入条件
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with entry columns populated
"""
# 多头买入信号 - 基于x.py的buy_signal逻辑
dataframe.loc[
(
# Signal: RSI crosses above 30
(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value))
& (dataframe["tema"] <= dataframe["bb_middleband"]) # Guard: tema below BB middle
& (dataframe["tema"] > dataframe["tema"].shift(1)) # Guard: tema is raising
& (dataframe["volume"] > 0) # Make sure Volume is not 0
# 价格在MA60上方且距离MA60不超过10%
dataframe["price_above_ma60"] &
# MA60呈上升趋势过去5天中至少3天上升
dataframe["ma_uptrend"] &
# MA60数据有效
dataframe['ma60'].notna() &
# 成交量放大当日成交量大于过去5日平均
dataframe["volume_surge"] &
# 前5天中下跌天数不超过2天
dataframe["not_too_many_downs"] &
# 当天收盘价大于5天前的价格
dataframe["price_higher_than_5days_ago"] &
# 价格在MA120上方
dataframe['price_above_ma120'] &
# MA120过去5天不是下降趋势
dataframe['ma120_not_downtrend'] &
# RSI小于阈值
dataframe['rsi_lt_threshold'] &
# 确保成交量不为0
(dataframe["volume"] > 0)
),
"enter_long",
] = 1
dataframe.loc[
(
# Signal: RSI crosses above 70
(qtpylib.crossed_above(dataframe["rsi"], self.short_rsi.value))
& (dataframe["tema"] > dataframe["bb_middleband"]) # Guard: tema above BB middle
& (dataframe["tema"] < dataframe["tema"].shift(1)) # Guard: tema is falling
& (dataframe["volume"] > 0) # Make sure Volume is not 0
),
"enter_short",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the exit signal for the given dataframe
基于x.py策略的卖出信号逻辑
实现MA60/MA120趋势跟踪策略的卖出条件
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with exit columns populated
"""
# 多头卖出信号 - 基于x.py的sell_signal逻辑
# dataframe.loc[
# (
# # 价格跌破MA60且跌幅超过10%
# (~dataframe['price_above_ma60']) &
# ((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
# dataframe['ma60'].notna() &
# # 确保成交量不为0
# (dataframe["volume"] > 0)
# ),
# "exit_long",
# ] = 1
dataframe.loc[
(
# Signal: RSI crosses above 70
(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value))
& (dataframe["tema"] > dataframe["bb_middleband"]) # Guard: tema above BB middle
& (dataframe["tema"] < dataframe["tema"].shift(1)) # Guard: tema is falling
& (dataframe["volume"] > 0) # Make sure Volume is not 0
# 价格跌破MA60且跌幅超过10%
(dataframe['close'] < dataframe['ma120']) &
dataframe['ma120'].notna() &
# 确保成交量不为0
(dataframe["volume"] > 0)
),
"exit_long",
] = 1
dataframe.loc[
(
# Signal: RSI crosses above 30
(qtpylib.crossed_above(dataframe["rsi"], self.exit_short_rsi.value))
&
# Guard: tema below BB middle
(dataframe["tema"] <= dataframe["bb_middleband"])
& (dataframe["tema"] > dataframe["tema"].shift(1)) # Guard: tema is raising
& (dataframe["volume"] > 0) # Make sure Volume is not 0
),
"exit_short",
] = 1
return dataframe