Momo Trades V3 策略
概览
Momo Trades V3 是将原始 MetaTrader 专家顾问迁移到 StockSharp 框架的动量策略。它保留了 EA 的核心思路:通过多条件 MACD 形态识别配合位移 EMA 滤波,同时增加可选的保本管理以及与原版相似的自动仓位控制模式。
交易逻辑
- MACD 动量形态:使用标准参数
(12, 26, 9)和额外的历史位移MacdShift。做多信号包含两种情况:- MACD 主线连续上升,第 3 个值等于 0,之后两个值继续上升;
- MACD 上穿零轴,当前值和之后的值保持为正,而更早的值仍为负。 做空信号则要求上述条件镜像成立。
- EMA 距离过滤:移位后的收盘价(
MaShift)必须相对 EMA 至少偏离PriceShiftPoints个 MetaTrader 点。多头要求价格在 EMA 上方,空头要求在 EMA 下方,以避免在均线附近追单。 - 单次持仓模式:仅在当前无仓位时才允许开新仓。持仓期间出现的反向信号被忽略。
- 日终平仓:启用
CloseEndDay后,策略会在平台时间 23:00(周五为 21:00)平掉所有仓位,规避隔夜风险。 - 保本管理:
UseBreakeven打开时,一旦价格运行足够距离,可将止损移动到入场价加BreakevenOffsetPoints,策略即记录该水平;若价格回落(或回升)至该水平,便立即以市价离场。
风险控制
- 初始保护:
StopLossPoints与TakeProfitPoints会通过合约的PriceStep转换为绝对价差,并传入StartProtection,因此止损/止盈订单会自动附加在仓位上。 - 自动仓位:若
UseAutoVolume为真,订单数量根据当前资产权益计算。策略取权益的RiskFraction,除以合约名义价值(价格 × 合约手数),再根据交易所的VolumeStep规范化,并遵守VolumeMin/VolumeMax限制。关闭自动模式时直接使用固定的TradeVolume。
指标
- MACD:提供主要的动量信号,并结合
MacdShift对历史样本进行评估。 - EMA:作为趋势/距离过滤器。
参数
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CandleType |
DataType |
TimeFrame(15m) |
生成信号所用的主时间框架。 |
MaPeriod |
int |
22 |
EMA 滤波的周期。 |
MaShift |
int |
1 |
取样收盘价与 EMA 时所使用的已完成柱数。 |
FastPeriod |
int |
12 |
MACD 的快速 EMA 周期。 |
SlowPeriod |
int |
26 |
MACD 的慢速 EMA 周期。 |
SignalPeriod |
int |
9 |
MACD 信号线 EMA 周期。 |
MacdShift |
int |
1 |
计算 MACD 形态时的额外位移。 |
PriceShiftPoints |
decimal |
10 |
移位收盘价与 EMA 的最小距离(MetaTrader 点)。 |
TradeVolume |
decimal |
0.1 |
未启用自动仓位时的基础手数。 |
RiskFraction |
decimal |
0.1 |
自动仓位模式下占用的权益比例。 |
UseAutoVolume |
bool |
false |
是否启用风险驱动的仓位计算。 |
StopLossPoints |
decimal |
100 |
初始止损距离(MetaTrader 点),0 表示不设置硬性止损。 |
TakeProfitPoints |
decimal |
0 |
初始止盈距离,0 表示不设置固定目标。 |
CloseEndDay |
bool |
true |
是否在交易日结束时强制平仓。 |
UseBreakeven |
bool |
false |
是否启用保本管理。 |
BreakevenOffsetPoints |
decimal |
0 |
移动至保本价时附加的偏移量。 |
使用建议
- 请确保标的提供有效的
PriceStep。若缺失数据,策略会退回到0.0001作为点值换算因子。 - 策略仅在蜡烛结束时处理信号,以保持与原 EA 行为一致。
- 因为同一时间只持有一笔仓位,单笔风险完全由
TradeVolume(或自动计算的手数)决定。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Momentum-based strategy converted from the "Momo Trades V3" MetaTrader expert.
/// The system combines MACD momentum patterns with a displaced EMA filter and optional breakeven management.
/// </summary>
public class MomoTradesV3Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _emaShift;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _macdShift;
private readonly StrategyParam<decimal> _macdZeroTolerance;
private readonly StrategyParam<decimal> _priceShiftPoints;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _riskFraction;
private readonly StrategyParam<bool> _useAutoVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _closeEndDay;
private readonly StrategyParam<bool> _useBreakeven;
private readonly StrategyParam<decimal> _breakevenOffsetPoints;
private readonly List<decimal> _macdHistory = new();
private readonly List<decimal> _emaHistory = new();
private readonly List<decimal> _closeHistory = new();
private decimal _pointValue;
private decimal? _breakevenPrice;
private Sides? _breakevenSide;
private int _candlesSinceLastOrder;
private const int CooldownCandles = 200;
/// <summary>
/// Primary candle type for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// EMA period used for the directional filter.
/// </summary>
public int MaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Number of finished bars used when sampling the EMA and close prices.
/// </summary>
public int MaShift
{
get => _emaShift.Value;
set => _emaShift.Value = value;
}
/// <summary>
/// Fast EMA length for the MACD indicator.
/// </summary>
public int FastPeriod
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Slow EMA length for the MACD indicator.
/// </summary>
public int SlowPeriod
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Signal EMA length for the MACD indicator.
/// </summary>
public int SignalPeriod
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Additional bar displacement applied when reading MACD values.
/// </summary>
public int MacdShift
{
get => _macdShift.Value;
set => _macdShift.Value = value;
}
/// <summary>
/// Absolute tolerance used to treat MACD values as neutral.
/// </summary>
public decimal MacdZeroTolerance
{
get => _macdZeroTolerance.Value;
set => _macdZeroTolerance.Value = value;
}
/// <summary>
/// Minimal distance between price and EMA expressed in MetaTrader points.
/// </summary>
public decimal PriceShiftPoints
{
get => _priceShiftPoints.Value;
set => _priceShiftPoints.Value = value;
}
/// <summary>
/// Base trading volume.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Fraction of portfolio equity allocated when auto volume is enabled.
/// </summary>
public decimal RiskFraction
{
get => _riskFraction.Value;
set => _riskFraction.Value = value;
}
/// <summary>
/// Enable automatic position sizing.
/// </summary>
public bool UseAutoVolume
{
get => _useAutoVolume.Value;
set => _useAutoVolume.Value = value;
}
/// <summary>
/// Initial protective stop distance in MetaTrader points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Initial take-profit distance in MetaTrader points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Close any open position at the end of the trading day.
/// </summary>
public bool CloseEndDay
{
get => _closeEndDay.Value;
set => _closeEndDay.Value = value;
}
/// <summary>
/// Enable breakeven stop handling.
/// </summary>
public bool UseBreakeven
{
get => _useBreakeven.Value;
set => _useBreakeven.Value = value;
}
/// <summary>
/// Offset applied to the breakeven trigger in MetaTrader points.
/// </summary>
public decimal BreakevenOffsetPoints
{
get => _breakevenOffsetPoints.Value;
set => _breakevenOffsetPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MomoTradesV3Strategy"/>.
/// </summary>
public MomoTradesV3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_emaPeriod = Param(nameof(MaPeriod), 22)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Length of the EMA filter", "Indicators");
_emaShift = Param(nameof(MaShift), 1)
.SetGreaterThanZero()
.SetDisplay("EMA Shift", "Number of closed bars used for EMA comparison", "Indicators");
_macdFast = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA period", "Indicators");
_macdSlow = Param(nameof(SlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA period", "Indicators");
_macdSignal = Param(nameof(SignalPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal EMA period", "Indicators");
_macdShift = Param(nameof(MacdShift), 1)
.SetGreaterThanZero()
.SetDisplay("MACD Shift", "Extra displacement for MACD history", "Indicators");
_macdZeroTolerance = Param(nameof(MacdZeroTolerance), 1e-8m)
.SetGreaterThanZero()
.SetDisplay("MACD Zero Tolerance", "Absolute tolerance for flat MACD detection", "Indicators");
_priceShiftPoints = Param(nameof(PriceShiftPoints), 10m)
.SetDisplay("Price Shift", "Required price offset from EMA in MetaTrader points", "Signals");
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Default trade volume", "Trading");
_riskFraction = Param(nameof(RiskFraction), 0.1m)
.SetDisplay("Risk Fraction", "Equity fraction for auto volume", "Risk Management");
_useAutoVolume = Param(nameof(UseAutoVolume), false)
.SetDisplay("Auto Volume", "Enable risk-based volume sizing", "Risk Management");
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetDisplay("Stop-Loss Points", "Distance to the initial stop in MetaTrader points", "Risk Management");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
.SetDisplay("Take-Profit Points", "Distance to the initial take-profit in MetaTrader points", "Risk Management");
_closeEndDay = Param(nameof(CloseEndDay), true)
.SetDisplay("Close End of Day", "Exit positions near the session close", "Risk Management");
_useBreakeven = Param(nameof(UseBreakeven), false)
.SetDisplay("Use Breakeven", "Move the stop to breakeven after profit", "Risk Management");
_breakevenOffsetPoints = Param(nameof(BreakevenOffsetPoints), 0m)
.SetDisplay("Breakeven Offset", "Additional points added to the breakeven level", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdHistory.Clear();
_emaHistory.Clear();
_closeHistory.Clear();
_breakevenPrice = null;
_breakevenSide = null;
_pointValue = 0;
_candlesSinceLastOrder = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 0m;
if (_pointValue <= 0m)
{
var decimals = Security?.Decimals;
if (decimals != null)
{
_pointValue = (decimal)Math.Pow(10, -decimals.Value);
}
if (_pointValue <= 0m)
_pointValue = 0.0001m;
}
Volume = TradeVolume;
var macd = new MovingAverageConvergenceDivergence();
macd.ShortMa.Length = FastPeriod;
macd.LongMa.Length = SlowPeriod;
var ema = new ExponentialMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(macd, ema, ProcessCandle)
.Start();
var stopUnit = StopLossPoints > 0m && _pointValue > 0m
? new Unit(StopLossPoints * _pointValue, UnitTypes.Absolute)
: null;
var takeUnit = TakeProfitPoints > 0m && _pointValue > 0m
? new Unit(TakeProfitPoints * _pointValue, UnitTypes.Absolute)
: null;
StartProtection(takeProfit: takeUnit, stopLoss: stopUnit);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, macd);
}
}
private void ProcessCandle(ICandleMessage candle, decimal macdLine, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
_macdHistory.Insert(0, macdLine);
TrimHistory(_macdHistory);
_emaHistory.Insert(0, emaValue);
TrimHistory(_emaHistory);
_closeHistory.Insert(0, candle.ClosePrice);
TrimHistory(_closeHistory);
_candlesSinceLastOrder++;
if (!IsFormedAndOnlineAndAllowTrading())
return;
HandleEndOfDayExit(candle);
HandleBreakeven(candle);
if (Position != 0)
return;
if (_candlesSinceLastOrder < CooldownCandles)
return;
var priceShift = PriceShiftPoints * _pointValue;
var canBuy = EvaluateMacdBuy() && EvaluateEmaBuy(priceShift);
var canSell = EvaluateMacdSell() && EvaluateEmaSell(priceShift);
if (canBuy)
{
var volume = CalculateOrderVolume(candle.ClosePrice);
volume = NormalizeVolume(volume);
if (volume > 0m)
{
BuyMarket(volume);
_candlesSinceLastOrder = 0;
}
}
else if (canSell)
{
var volume = CalculateOrderVolume(candle.ClosePrice);
volume = NormalizeVolume(volume);
if (volume > 0m)
{
SellMarket(volume);
_candlesSinceLastOrder = 0;
}
}
}
private void HandleEndOfDayExit(ICandleMessage candle)
{
if (!CloseEndDay || Position == 0)
return;
var endHour = candle.OpenTime.DayOfWeek == DayOfWeek.Friday ? 21 : 23;
if (candle.OpenTime.Hour != endHour)
return;
if (Position > 0)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0)
{
BuyMarket(Math.Abs(Position));
}
_breakevenPrice = null;
_breakevenSide = null;
}
private void HandleBreakeven(ICandleMessage candle)
{
if (!UseBreakeven || Position == 0)
{
_breakevenPrice = null;
_breakevenSide = null;
return;
}
var entryPrice = candle.ClosePrice;
var offset = BreakevenOffsetPoints * _pointValue;
if (Position > 0)
{
if (_breakevenSide != Sides.Buy)
{
_breakevenSide = Sides.Buy;
_breakevenPrice = null;
}
var desired = entryPrice + offset;
if (_breakevenPrice == null)
{
if (candle.LowPrice > desired)
{
_breakevenPrice = desired;
}
}
else if (candle.LowPrice <= _breakevenPrice.Value)
{
SellMarket(Math.Abs(Position));
_breakevenPrice = null;
_breakevenSide = null;
}
}
else if (Position < 0)
{
if (_breakevenSide != Sides.Sell)
{
_breakevenSide = Sides.Sell;
_breakevenPrice = null;
}
var desired = entryPrice - offset;
if (_breakevenPrice == null)
{
if (candle.HighPrice < desired)
{
_breakevenPrice = desired;
}
}
else if (candle.HighPrice >= _breakevenPrice.Value)
{
BuyMarket(Math.Abs(Position));
_breakevenPrice = null;
_breakevenSide = null;
}
}
}
private bool EvaluateMacdBuy()
{
if (!TryGetMacd(MacdShift + 3, out var macd3) ||
!TryGetMacd(MacdShift + 4, out var macd4) ||
!TryGetMacd(MacdShift + 5, out var macd5) ||
!TryGetMacd(MacdShift + 6, out var macd6) ||
!TryGetMacd(MacdShift + 7, out var macd7) ||
!TryGetMacd(MacdShift + 8, out var macd8))
{
return false;
}
var macd5IsZero = Math.Abs(macd5) <= MacdZeroTolerance;
var pattern1 = macd3 > macd4 &&
macd4 > macd5 &&
macd5IsZero &&
macd5 > macd6 &&
macd6 > macd7;
var pattern2 = macd3 > macd4 &&
macd4 > macd5 &&
macd5 >= 0m &&
macd6 <= 0m &&
macd6 > macd7 &&
macd7 > macd8;
return pattern1 || pattern2;
}
private bool EvaluateMacdSell()
{
if (!TryGetMacd(MacdShift + 3, out var macd3) ||
!TryGetMacd(MacdShift + 4, out var macd4) ||
!TryGetMacd(MacdShift + 5, out var macd5) ||
!TryGetMacd(MacdShift + 6, out var macd6) ||
!TryGetMacd(MacdShift + 7, out var macd7) ||
!TryGetMacd(MacdShift + 8, out var macd8))
{
return false;
}
var macd5IsZero = Math.Abs(macd5) <= MacdZeroTolerance;
var pattern1 = macd3 < macd4 &&
macd4 < macd5 &&
macd5IsZero &&
macd5 < macd6 &&
macd6 < macd7;
var pattern2 = macd3 < macd4 &&
macd4 < macd5 &&
macd5 <= 0m &&
macd6 >= 0m &&
macd6 < macd7 &&
macd7 < macd8;
return pattern1 || pattern2;
}
private bool EvaluateEmaBuy(decimal priceShift)
{
if (!TryGetShiftedValue(_closeHistory, MaShift, out var close) ||
!TryGetShiftedValue(_emaHistory, MaShift, out var ema))
{
return false;
}
return close - ema > priceShift;
}
private bool EvaluateEmaSell(decimal priceShift)
{
if (!TryGetShiftedValue(_closeHistory, MaShift, out var close) ||
!TryGetShiftedValue(_emaHistory, MaShift, out var ema))
{
return false;
}
return ema - close > priceShift;
}
private bool TryGetMacd(int index, out decimal value)
{
if (index < 0 || index >= _macdHistory.Count)
{
value = 0m;
return false;
}
value = _macdHistory[index];
return true;
}
private static bool TryGetShiftedValue(List<decimal> list, int shift, out decimal value)
{
if (shift < 0 || shift >= list.Count)
{
value = 0m;
return false;
}
value = list[shift];
return true;
}
private static void TrimHistory(List<decimal> list)
{
const int maxItems = 64;
if (list.Count > maxItems)
list.RemoveRange(maxItems, list.Count - maxItems);
}
private decimal CalculateOrderVolume(decimal price)
{
if (!UseAutoVolume)
return TradeVolume;
var portfolio = Portfolio;
var security = Security;
if (portfolio?.CurrentValue == null || security == null || price <= 0m || RiskFraction <= 0m)
return TradeVolume;
var equity = portfolio.CurrentValue.Value;
if (equity <= 0m)
return TradeVolume;
var lotSize = security.Multiplier ?? 1m;
if (lotSize <= 0m)
lotSize = 1m;
var contractValue = price * lotSize;
if (contractValue <= 0m)
return TradeVolume;
var desired = equity * RiskFraction / contractValue;
var normalized = NormalizeVolume(desired);
return normalized > 0m ? normalized : TradeVolume;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var sec = Security;
if (sec == null)
return volume;
var step = sec.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var steps = Math.Floor(volume / step);
var normalized = (decimal)steps * step;
var min = sec.MinVolume ?? step;
if (normalized < min)
normalized = min;
var max = sec.MaxVolume;
if (max != null && normalized > max.Value)
normalized = max.Value;
return normalized;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergence, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class momo_trades_v3_strategy(Strategy):
"""
Momo Trades V3: MACD momentum patterns with displaced EMA filter.
Simplified from C# (no breakeven, no auto volume).
"""
def __init__(self):
super(momo_trades_v3_strategy, self).__init__()
self._ema_period = self.Param("MaPeriod", 22).SetDisplay("EMA Period", "EMA filter length", "Indicators")
self._ema_shift = self.Param("MaShift", 1).SetDisplay("EMA Shift", "Bars offset for EMA", "Indicators")
self._macd_fast = self.Param("FastPeriod", 12).SetDisplay("MACD Fast", "Fast EMA", "Indicators")
self._macd_slow = self.Param("SlowPeriod", 26).SetDisplay("MACD Slow", "Slow EMA", "Indicators")
self._macd_shift = self.Param("MacdShift", 1).SetDisplay("MACD Shift", "MACD history offset", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 20).SetDisplay("Cooldown", "Min bars between entries", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._macd_history = []
self._ema_history = []
self._close_history = []
self._bars_from_signal = 20
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(momo_trades_v3_strategy, self).OnReseted()
self._macd_history = []
self._ema_history = []
self._close_history = []
self._bars_from_signal = self._cooldown_bars.Value
def OnStarted2(self, time):
super(momo_trades_v3_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self._macd_fast.Value
macd.LongMa.Length = self._macd_slow.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(macd, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, macd_val, ema_val):
if candle.State != CandleStates.Finished:
return
macd_f = float(macd_val)
ema_f = float(ema_val)
close = float(candle.ClosePrice)
self._macd_history.insert(0, macd_f)
self._ema_history.insert(0, ema_f)
self._close_history.insert(0, close)
if len(self._macd_history) > 64:
self._macd_history = self._macd_history[:64]
if len(self._ema_history) > 64:
self._ema_history = self._ema_history[:64]
if len(self._close_history) > 64:
self._close_history = self._close_history[:64]
self._bars_from_signal += 1
if self.Position != 0:
return
shift = self._macd_shift.Value
ema_shift = self._ema_shift.Value
can_buy = self._eval_macd_buy(shift) and self._eval_ema_buy(ema_shift)
can_sell = self._eval_macd_sell(shift) and self._eval_ema_sell(ema_shift)
if self._bars_from_signal >= self._cooldown_bars.Value and can_buy:
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= self._cooldown_bars.Value and can_sell:
self.SellMarket()
self._bars_from_signal = 0
def _get_macd(self, idx):
if idx < 0 or idx >= len(self._macd_history):
return None
return self._macd_history[idx]
def _eval_macd_buy(self, shift):
vals = [self._get_macd(shift + i) for i in range(3, 9)]
if any(v is None for v in vals):
return False
m3, m4, m5, m6, m7, m8 = vals
p1 = m3 > m4 and m4 > m5 and abs(m5) < 1e-8 and m5 > m6 and m6 > m7
p2 = m3 > m4 and m4 > m5 and m5 >= 0 and m6 <= 0 and m6 > m7 and m7 > m8
return p1 or p2
def _eval_macd_sell(self, shift):
vals = [self._get_macd(shift + i) for i in range(3, 9)]
if any(v is None for v in vals):
return False
m3, m4, m5, m6, m7, m8 = vals
p1 = m3 < m4 and m4 < m5 and abs(m5) < 1e-8 and m5 < m6 and m6 < m7
p2 = m3 < m4 and m4 < m5 and m5 <= 0 and m6 >= 0 and m6 < m7 and m7 < m8
return p1 or p2
def _eval_ema_buy(self, shift):
if shift >= len(self._close_history) or shift >= len(self._ema_history):
return False
return self._close_history[shift] - self._ema_history[shift] > 0
def _eval_ema_sell(self, shift):
if shift >= len(self._close_history) or shift >= len(self._ema_history):
return False
return self._ema_history[shift] - self._close_history[shift] > 0
def CreateClone(self):
return momo_trades_v3_strategy()