添加策略
This commit is contained in:
183
.gitignore
vendored
Normal file
183
.gitignore
vendored
Normal 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
14
Dockerfile
Normal 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"]
|
||||
@@ -10,4 +10,4 @@ services:
|
||||
command: >
|
||||
trade
|
||||
--config ./user_data/config.json
|
||||
--strategy MACDStrategy
|
||||
--strategy SimpleRSIStrategyFixed
|
||||
@@ -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": [
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
66
user_data/strategies/SimpleRSIStrategyOptimized.py
Normal file
66
user_data/strategies/SimpleRSIStrategyOptimized.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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/
|
||||
|
||||
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
|
||||
|
||||
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_*
|
||||
基于x.py策略的MA60/MA120趋势跟踪策略
|
||||
|
||||
策略逻辑:
|
||||
1. 买入条件:
|
||||
- 价格在MA60上方且距离MA60不超过10%
|
||||
- MA60呈上升趋势(过去5天中至少3天上升)
|
||||
- 成交量放大(当日成交量大于过去5日平均)
|
||||
- 前5天中下跌天数不超过2天
|
||||
- 当天收盘价大于5天前的价格
|
||||
- 价格在MA120上方
|
||||
- MA120过去5天不是下降趋势
|
||||
- RSI小于阈值(默认70)
|
||||
|
||||
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
|
||||
# 基础移动平均线指标
|
||||
# ------------------------------------
|
||||
|
||||
# MA60 - 使用可优化参数
|
||||
dataframe['ma60'] = ta.SMA(dataframe, timeperiod=60)
|
||||
|
||||
# MA120 - 使用可优化参数
|
||||
dataframe['ma120'] = ta.SMA(dataframe, timeperiod=120)
|
||||
|
||||
# RSI指标
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
|
||||
# 成交量相关指标
|
||||
# ------------------------------------
|
||||
|
||||
# 成交量5日移动平均
|
||||
dataframe['volume_5ma'] = dataframe['volume'].rolling(window=5).mean()
|
||||
|
||||
# 成交量放大信号:当日成交量大于过去5日平均的倍数
|
||||
dataframe['volume_surge'] = dataframe['volume'] > (dataframe['volume_5ma'] * 1)
|
||||
|
||||
# 价格与MA60关系指标
|
||||
# ------------------------------------
|
||||
|
||||
# ADX
|
||||
dataframe["adx"] = ta.ADX(dataframe)
|
||||
|
||||
# # Plus Directional Indicator / Movement
|
||||
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||
|
||||
# # Minus Directional Indicator / Movement
|
||||
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
|
||||
# # 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"]
|
||||
# 价格在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"]
|
||||
# )
|
||||
|
||||
# # 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)
|
||||
|
||||
# # 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)
|
||||
|
||||
# Parabolic SAR
|
||||
dataframe["sar"] = ta.SAR(dataframe)
|
||||
|
||||
# TEMA - Triple Exponential Moving Average
|
||||
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
|
||||
|
||||
# Cycle Indicator
|
||||
|
||||
# 价格在MA120上方
|
||||
dataframe['price_above_ma120'] = dataframe['close'] > dataframe['ma120']
|
||||
|
||||
# 前一天在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)
|
||||
)
|
||||
|
||||
# 前一天在MA60下方
|
||||
dataframe['prev_below_ma'] = dataframe['close'].shift(1) < dataframe['ma60'].shift(1)
|
||||
|
||||
# 突破信号:前一天在MA60附近或下方,当天在MA60上方
|
||||
dataframe['breakthrough'] = (
|
||||
(dataframe['prev_below_ma'] | dataframe['prev_near_ma']) &
|
||||
dataframe['price_above_ma60']
|
||||
)
|
||||
|
||||
# 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']
|
||||
|
||||
# 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]
|
||||
"""
|
||||
|
||||
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']
|
||||
)
|
||||
|
||||
# 卖出信号:价格跌破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
|
||||
|
||||
Reference in New Issue
Block a user