Macd Pattern Trader v03(StockSharp 移植版)
概述
Macd Pattern Trader v03 是从 MetaTrader 4 指标顾问 MacdPatternTraderv03 转换而来的 StockSharp 策略。原始 EA 通过观察 MACD 主线的三重峰/谷模式并结合移动平均线的分批止盈来寻找趋势衰竭。本 C# 版本复现了核心逻辑,并使用 StockSharp 的烛线订阅、指标绑定与市价下单接口。
策略主要针对流动性较好的外汇品种,默认使用 30 分钟 K 线,与原脚本保持一致;默认手数为 1(在 StockSharp 中表示一个合约或等价手数)。
指标与数据流程
- MACD(快 EMA=5、慢 EMA=13、信号=1):仅使用 MACD 主线判断模式,信号线不参与决策。
- EMA(7)、EMA(21):用于仓位管理的短期与中期均线。
- SMA(98)、EMA(365):用于二次减仓触发的慢速滤波器。
策略通过 SubscribeCandles 订阅所选周期的蜡烛,并通过 Bind/BindEx 将指标与回调绑定,确保只在 K 线收盘之后执行逻辑。
入场规则
做空模式
- 当 MACD 主线上穿“上方激活阈值”(默认 0.0030)时准备做空。
- 如果 MACD 在阈值上方形成局部顶点且随后跌破“上方确认阈值”(默认 0.0045),记录第一个峰值。
- 若 MACD 再次回到确认阈值之上并形成更高的局部顶点,随后再度跌回阈值下方,则记录第二个峰值。
- 当第三次回落出现,并且 MACD 连续三根 K 线都位于确认阈值下方,同时最后一个顶点低于前一个顶点,则确认做空模式。
- 清空所有多头持仓后,以设定手数开立空单。
做多模式
- 当 MACD 主线跌破“下方激活阈值”(默认 −0.0030)时准备做多。
- 如果 MACD 在阈值下方形成局部低点且随后上穿“下方确认阈值”(默认 −0.0045),记录第一个谷值。
- 若 MACD 再次跌回阈值下方并形成更低的局部低点,随后上穿阈值,则记录第二个谷值。
- 当第三次上冲出现,并且 MACD 连续三根 K 线位于确认阈值上方,同时最新谷值高于上一谷值时,确认做多模式。
- 平掉所有空头持仓后,以设定手数买入。
上述逻辑完整复刻了 MQ4 代码中的 stops、stops1 与 aop_ok* 状态机,并在 MACD 回到激活带内时立即复位。
仓位管理
- 分批止盈:当未实现盈亏(
(收盘价 − 开仓价) * 持仓量)超过ProfitThreshold(默认 5 个价格单位)时启动分批减仓。- 阶段一(多头):上一根 K 线的收盘价需位于 EMA(21) 之上,卖出初始多头仓位的三分之一。空头镜像条件为收盘价低于 EMA(21),并买回初始空头仓位的三分之一。
- 阶段二(多头):上一根 K 线最高价需突破 SMA(98) 与 EMA(365) 的平均值,卖出初始仓位的一半;空头要求最低价跌破相同均值,并买回一半仓位。
- 剩余持仓:按照原 EA 的做法,剩余仓位不再自动处理。
- 风险控制:原脚本根据历史高低点设置止损/止盈。本移植版未自动挂出保护单,如需硬止损可结合
StartProtection()或外部风控模块。
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
Volume |
1 | 每次入场的下单手数。 |
CandleType |
30 分钟 K 线 | 指标计算所用的蜡烛类型。 |
FastEmaLength / SlowEmaLength |
5 / 13 | MACD 的快慢均线周期。 |
UpperThreshold / LowerThreshold |
0.0045 / −0.0045 | 确认形态的阈值带。 |
UpperActivation / LowerActivation |
0.0030 / −0.0030 | 激活形态的外层阈值。 |
EmaOneLength / EmaTwoLength |
7 / 21 | 可视化与分批逻辑使用的 EMA 周期。 |
SmaLength |
98 | 与 EMA(365) 组合形成第二阶段触发。 |
EmaFourLength |
365 | 长期 EMA,用于第二阶段条件。 |
ProfitThreshold |
5 | 触发减仓所需的最低未实现盈亏(价格×数量单位)。 |
使用建议
- 仅在支持部分减仓的撮合/柜台上使用,策略会按 1/3 与 1/2 的比例执行市价减仓。
- 未自动附加保护单,若需要固定止损,可在策略启动后调用
StartProtection()或组合其他风控策略。 ProfitThreshold以价格×数量计量,实际使用时需根据品种的点值或最小变动价位调整,才能与原脚本中的“5 个货币单位”保持一致。- MACD 模式需要较平滑的走势,若在噪声较大的品种上运行,触发概率会显著下降。
与 MQ4 版本的差异
- 使用 StockSharp 指标绑定替代了重复的
iMACD调用。 - 未实现盈亏通过
Position与PositionAvgPrice计算,可能与 MetaTrader 中的OrderProfit()略有差异。 - 未自动生成止损/止盈委托,需要额外的风险控制模块。
- 原脚本中的
sum_bars_bup参数在 MQ4 中也未被使用,因此在移植版中省略。
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>
/// MACD pattern strategy inspired by the MetaTrader advisor "MacdPatternTraderv03".
/// </summary>
public class MacdPatternTraderV03Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<decimal> _upperThreshold;
private readonly StrategyParam<decimal> _upperActivation;
private readonly StrategyParam<decimal> _lowerThreshold;
private readonly StrategyParam<decimal> _lowerActivation;
private readonly StrategyParam<int> _emaOneLength;
private readonly StrategyParam<int> _emaTwoLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _emaFourLength;
private readonly StrategyParam<decimal> _profitThreshold;
private decimal? _previousMacd;
private decimal? _olderMacd;
private decimal _entryPrice;
private bool _isAboveUpperActivation;
private bool _firstUpperDropConfirmed;
private bool _secondUpperDropConfirmed;
private bool _sellReady;
private decimal _firstUpperPeak;
private decimal _secondUpperPeak;
private bool _isBelowLowerActivation;
private bool _firstLowerRiseConfirmed;
private bool _secondLowerRiseConfirmed;
private bool _buyReady;
private decimal _firstLowerTrough;
private decimal _secondLowerTrough;
private decimal? _emaTwoValue;
private decimal? _smaValue;
private decimal? _emaFourValue;
private ICandleMessage _previousCandle;
private int _longScaleStage;
private int _shortScaleStage;
private decimal _initialLongPosition;
private decimal _initialShortPosition;
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public MacdPatternTraderV03Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for calculations", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 5)
.SetDisplay("Fast EMA", "Fast period used inside MACD", "MACD");
_slowEmaLength = Param(nameof(SlowEmaLength), 13)
.SetDisplay("Slow EMA", "Slow period used inside MACD", "MACD");
_upperThreshold = Param(nameof(UpperThreshold), 50m)
.SetDisplay("Upper Threshold", "Level that confirms bearish exhaustion", "MACD");
_upperActivation = Param(nameof(UpperActivation), 30m)
.SetDisplay("Upper Activation", "Level that arms the bearish pattern", "MACD");
_lowerThreshold = Param(nameof(LowerThreshold), -50m)
.SetDisplay("Lower Threshold", "Level that confirms bullish exhaustion", "MACD");
_lowerActivation = Param(nameof(LowerActivation), -30m)
.SetDisplay("Lower Activation", "Level that arms the bullish pattern", "MACD");
_emaOneLength = Param(nameof(EmaOneLength), 7)
.SetDisplay("EMA #1", "Short EMA used for scaling out", "Management");
_emaTwoLength = Param(nameof(EmaTwoLength), 21)
.SetDisplay("EMA #2", "Second EMA used for scaling out", "Management");
_smaLength = Param(nameof(SmaLength), 98)
.SetDisplay("SMA", "Simple moving average used for scaling out", "Management");
_emaFourLength = Param(nameof(EmaFourLength), 365)
.SetDisplay("EMA #4", "Slow EMA used for scaling out", "Management");
_profitThreshold = Param(nameof(ProfitThreshold), 5m)
.SetDisplay("Profit Threshold", "Unrealized PnL required before scaling out", "Management");
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fast EMA length inside MACD.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Slow EMA length inside MACD.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// Upper threshold that marks MACD exhaustion for shorts.
/// </summary>
public decimal UpperThreshold
{
get => _upperThreshold.Value;
set => _upperThreshold.Value = value;
}
/// <summary>
/// Upper activation level that arms the short pattern.
/// </summary>
public decimal UpperActivation
{
get => _upperActivation.Value;
set => _upperActivation.Value = value;
}
/// <summary>
/// Lower threshold that marks MACD exhaustion for longs.
/// </summary>
public decimal LowerThreshold
{
get => _lowerThreshold.Value;
set => _lowerThreshold.Value = value;
}
/// <summary>
/// Lower activation level that arms the long pattern.
/// </summary>
public decimal LowerActivation
{
get => _lowerActivation.Value;
set => _lowerActivation.Value = value;
}
/// <summary>
/// Short EMA used for position management.
/// </summary>
public int EmaOneLength
{
get => _emaOneLength.Value;
set => _emaOneLength.Value = value;
}
/// <summary>
/// Second EMA used for position management.
/// </summary>
public int EmaTwoLength
{
get => _emaTwoLength.Value;
set => _emaTwoLength.Value = value;
}
/// <summary>
/// SMA length used for position management.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// Slow EMA used for position management.
/// </summary>
public int EmaFourLength
{
get => _emaFourLength.Value;
set => _emaFourLength.Value = value;
}
/// <summary>
/// Minimum unrealized PnL before scaling out (in price units * volume).
/// </summary>
public decimal ProfitThreshold
{
get => _profitThreshold.Value;
set => _profitThreshold.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergence();
macd.ShortMa.Length = FastEmaLength;
macd.LongMa.Length = SlowEmaLength;
var emaOne = new ExponentialMovingAverage { Length = EmaOneLength };
var emaTwo = new ExponentialMovingAverage { Length = EmaTwoLength };
var sma = new SimpleMovingAverage { Length = SmaLength };
var emaFour = new ExponentialMovingAverage { Length = EmaFourLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(macd, emaOne, emaTwo, sma, emaFour, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawIndicator(area, emaOne);
DrawIndicator(area, emaTwo);
DrawIndicator(area, sma);
DrawIndicator(area, emaFour);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal macdMain, decimal emaOne, decimal emaTwo, decimal sma, decimal emaFour)
{
if (candle.State != CandleStates.Finished)
return;
_emaTwoValue = emaTwo;
_smaValue = sma;
_emaFourValue = emaFour;
if (!IsFormedAndOnlineAndAllowTrading())
{
CacheMacd(macdMain);
_previousCandle = candle;
return;
}
if (_previousMacd is null || _olderMacd is null)
{
CacheMacd(macdMain);
_previousCandle = candle;
return;
}
var macdPrev = _previousMacd.Value;
var macdPrev2 = _olderMacd.Value;
EvaluateSellPattern(macdMain, macdPrev, macdPrev2);
EvaluateBuyPattern(macdMain, macdPrev, macdPrev2);
ManageOpenPosition(candle);
CacheMacd(macdMain);
_previousCandle = candle;
}
private void EvaluateSellPattern(decimal macdCurrent, decimal macdPrevious, decimal macdPrevious2)
{
if (macdCurrent > UpperActivation)
_isAboveUpperActivation = true;
if (_isAboveUpperActivation && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious > _firstUpperPeak && !_firstUpperDropConfirmed)
_firstUpperPeak = macdPrevious;
if (_firstUpperPeak > 0m && macdCurrent < UpperThreshold)
_firstUpperDropConfirmed = true;
if (macdCurrent < UpperActivation)
{
ResetSellPattern();
return;
}
if (_firstUpperDropConfirmed && macdCurrent > UpperThreshold && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious > _firstUpperPeak && macdPrevious > _secondUpperPeak && !_secondUpperDropConfirmed)
_secondUpperPeak = macdPrevious;
if (_secondUpperPeak > 0m && macdCurrent < UpperThreshold)
_secondUpperDropConfirmed = true;
if (_secondUpperDropConfirmed && macdCurrent < UpperThreshold && macdPrevious < UpperThreshold && macdPrevious2 < UpperThreshold && macdCurrent < macdPrevious && macdPrevious > macdPrevious2 && macdPrevious < _secondUpperPeak)
_sellReady = true;
if (!_sellReady)
return;
EnterShort();
}
private void EvaluateBuyPattern(decimal macdCurrent, decimal macdPrevious, decimal macdPrevious2)
{
if (macdCurrent < LowerActivation)
_isBelowLowerActivation = true;
if (_isBelowLowerActivation && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious < _firstLowerTrough && !_firstLowerRiseConfirmed)
_firstLowerTrough = macdPrevious;
if (_firstLowerTrough < 0m && macdCurrent > LowerThreshold)
_firstLowerRiseConfirmed = true;
if (macdCurrent > LowerActivation)
{
ResetBuyPattern();
return;
}
if (_firstLowerRiseConfirmed && macdCurrent < LowerThreshold && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious < _firstLowerTrough && macdPrevious < _secondLowerTrough && !_secondLowerRiseConfirmed)
_secondLowerTrough = macdPrevious;
if (_secondLowerTrough < 0m && macdCurrent > LowerThreshold)
_secondLowerRiseConfirmed = true;
if (_secondLowerRiseConfirmed && macdCurrent > LowerThreshold && macdPrevious > LowerThreshold && macdPrevious2 > LowerThreshold && macdCurrent > macdPrevious && macdPrevious < macdPrevious2 && macdPrevious > _secondLowerTrough)
_buyReady = true;
if (!_buyReady)
return;
EnterLong();
}
private void EnterShort()
{
var currentPosition = Position;
var flattenVolume = currentPosition > 0m ? currentPosition : 0m;
if (flattenVolume > 0m)
SellMarket(flattenVolume);
var entryVolume = Volume + Math.Max(0m, Position);
if (entryVolume <= 0m)
{
ResetSellPattern();
_sellReady = false;
return;
}
SellMarket(entryVolume);
_entryPrice = _previousCandle?.ClosePrice ?? 0m;
_initialShortPosition = Math.Abs(Position);
_shortScaleStage = 0;
_longScaleStage = 0;
_sellReady = false;
ResetSellPattern();
ResetBuyPattern();
}
private void EnterLong()
{
var currentPosition = Position;
var flattenVolume = currentPosition < 0m ? -currentPosition : 0m;
if (flattenVolume > 0m)
BuyMarket(flattenVolume);
var entryVolume = Volume + Math.Max(0m, -Position);
if (entryVolume <= 0m)
{
ResetBuyPattern();
_buyReady = false;
return;
}
BuyMarket(entryVolume);
_entryPrice = _previousCandle?.ClosePrice ?? 0m;
_initialLongPosition = Math.Max(0m, Position);
_longScaleStage = 0;
_shortScaleStage = 0;
_buyReady = false;
ResetBuyPattern();
ResetSellPattern();
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position == 0m)
{
_longScaleStage = 0;
_shortScaleStage = 0;
_initialLongPosition = 0m;
_initialShortPosition = 0m;
return;
}
var previousCandle = _previousCandle;
if (previousCandle is null)
return;
var profitThreshold = ProfitThreshold;
if (profitThreshold <= 0m)
return;
var unrealized = GetUnrealizedPnL(candle);
if (unrealized < profitThreshold)
return;
if (Position > 0m)
{
if (_emaTwoValue is decimal emaTwo && previousCandle.ClosePrice > emaTwo && _longScaleStage == 0)
{
var volume = Math.Min(Position, _initialLongPosition / 3m);
if (volume > 0m)
{
SellMarket(volume);
_longScaleStage = 1;
}
}
if (_smaValue is decimal sma && _emaFourValue is decimal emaFour && previousCandle.HighPrice > (sma + emaFour) / 2m && _longScaleStage == 1)
{
var volume = Math.Min(Position, _initialLongPosition / 2m);
if (volume > 0m)
{
SellMarket(volume);
_longScaleStage = 2;
}
}
}
else if (Position < 0m)
{
var shortPosition = -Position;
if (_emaTwoValue is decimal emaTwo && previousCandle.ClosePrice < emaTwo && _shortScaleStage == 0)
{
var volume = Math.Min(shortPosition, _initialShortPosition / 3m);
if (volume > 0m)
{
BuyMarket(volume);
_shortScaleStage = 1;
}
}
if (_smaValue is decimal sma && _emaFourValue is decimal emaFour && previousCandle.LowPrice < (sma + emaFour) / 2m && _shortScaleStage == 1)
{
var volume = Math.Min(shortPosition, _initialShortPosition / 2m);
if (volume > 0m)
{
BuyMarket(volume);
_shortScaleStage = 2;
}
}
}
}
private void CacheMacd(decimal macdValue)
{
_olderMacd = _previousMacd;
_previousMacd = macdValue;
}
private decimal GetUnrealizedPnL(ICandleMessage candle)
{
if (Position == 0m)
return 0m;
if (_entryPrice == 0m)
return 0m;
var diff = candle.ClosePrice - _entryPrice;
return diff * Position;
}
private void ResetSellPattern()
{
_isAboveUpperActivation = false;
_firstUpperDropConfirmed = false;
_secondUpperDropConfirmed = false;
_sellReady = false;
_firstUpperPeak = 0m;
_secondUpperPeak = 0m;
}
private void ResetBuyPattern()
{
_isBelowLowerActivation = false;
_firstLowerRiseConfirmed = false;
_secondLowerRiseConfirmed = false;
_buyReady = false;
_firstLowerTrough = 0m;
_secondLowerTrough = 0m;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousMacd = null;
_olderMacd = null;
_entryPrice = 0m;
_isAboveUpperActivation = false;
_firstUpperDropConfirmed = false;
_secondUpperDropConfirmed = false;
_sellReady = false;
_firstUpperPeak = 0m;
_secondUpperPeak = 0m;
_isBelowLowerActivation = false;
_firstLowerRiseConfirmed = false;
_secondLowerRiseConfirmed = false;
_buyReady = false;
_firstLowerTrough = 0m;
_secondLowerTrough = 0m;
_emaTwoValue = null;
_smaValue = null;
_emaFourValue = null;
_previousCandle = null;
_longScaleStage = 0;
_shortScaleStage = 0;
_initialLongPosition = 0m;
_initialShortPosition = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
MovingAverageConvergenceDivergence, ExponentialMovingAverage,
SimpleMovingAverage
)
class macd_pattern_trader_v03_strategy(Strategy):
def __init__(self):
super(macd_pattern_trader_v03_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Time frame used for calculations", "General")
self._fast_ema_length = self.Param("FastEmaLength", 5).SetDisplay("Fast EMA", "Fast period used inside MACD", "MACD")
self._slow_ema_length = self.Param("SlowEmaLength", 13).SetDisplay("Slow EMA", "Slow period used inside MACD", "MACD")
self._upper_threshold = self.Param("UpperThreshold", 50.0).SetDisplay("Upper Threshold", "Level that confirms bearish exhaustion", "MACD")
self._upper_activation = self.Param("UpperActivation", 30.0).SetDisplay("Upper Activation", "Level that arms the bearish pattern", "MACD")
self._lower_threshold = self.Param("LowerThreshold", -50.0).SetDisplay("Lower Threshold", "Level that confirms bullish exhaustion", "MACD")
self._lower_activation = self.Param("LowerActivation", -30.0).SetDisplay("Lower Activation", "Level that arms the bullish pattern", "MACD")
self._ema_one_length = self.Param("EmaOneLength", 7).SetDisplay("EMA 1", "Short EMA used for scaling out", "Management")
self._ema_two_length = self.Param("EmaTwoLength", 21).SetDisplay("EMA 2", "Second EMA used for scaling out", "Management")
self._sma_length = self.Param("SmaLength", 98).SetDisplay("SMA", "Simple moving average used for scaling out", "Management")
self._ema_four_length = self.Param("EmaFourLength", 365).SetDisplay("EMA 4", "Slow EMA used for scaling out", "Management")
self._profit_threshold = self.Param("ProfitThreshold", 5.0).SetDisplay("Profit Threshold", "Unrealized PnL required before scaling out", "Management")
self._previous_macd = None
self._older_macd = None
self._entry_price = 0.0
self._is_above_upper_activation = False
self._first_upper_drop_confirmed = False
self._second_upper_drop_confirmed = False
self._sell_ready = False
self._first_upper_peak = 0.0
self._second_upper_peak = 0.0
self._is_below_lower_activation = False
self._first_lower_rise_confirmed = False
self._second_lower_rise_confirmed = False
self._buy_ready = False
self._first_lower_trough = 0.0
self._second_lower_trough = 0.0
self._ema_two_value = None
self._sma_value = None
self._ema_four_value = None
self._previous_candle = None
self._long_scale_stage = 0
self._short_scale_stage = 0
self._initial_long_position = 0.0
self._initial_short_position = 0.0
@property
def CandleType(self): return self._candle_type.Value
@property
def FastEmaLength(self): return self._fast_ema_length.Value
@property
def SlowEmaLength(self): return self._slow_ema_length.Value
@property
def UpperThreshold(self): return self._upper_threshold.Value
@property
def UpperActivation(self): return self._upper_activation.Value
@property
def LowerThreshold(self): return self._lower_threshold.Value
@property
def LowerActivation(self): return self._lower_activation.Value
@property
def EmaOneLength(self): return self._ema_one_length.Value
@property
def EmaTwoLength(self): return self._ema_two_length.Value
@property
def SmaLength(self): return self._sma_length.Value
@property
def EmaFourLength(self): return self._ema_four_length.Value
@property
def ProfitThreshold(self): return self._profit_threshold.Value
def OnStarted2(self, time):
super(macd_pattern_trader_v03_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self.FastEmaLength
macd.LongMa.Length = self.SlowEmaLength
ema_one = ExponentialMovingAverage()
ema_one.Length = self.EmaOneLength
ema_two = ExponentialMovingAverage()
ema_two.Length = self.EmaTwoLength
sma = SimpleMovingAverage()
sma.Length = self.SmaLength
ema_four = ExponentialMovingAverage()
ema_four.Length = self.EmaFourLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(macd, ema_one, ema_two, sma, ema_four, self.ProcessCandle).Start()
def ProcessCandle(self, candle, macd_main, ema_one, ema_two, sma, ema_four):
if candle.State != CandleStates.Finished:
return
self._ema_two_value = float(ema_two)
self._sma_value = float(sma)
self._ema_four_value = float(ema_four)
mc = float(macd_main)
if not self.IsFormedAndOnlineAndAllowTrading():
self._cache_macd(mc)
self._previous_candle = candle
return
if self._previous_macd is None or self._older_macd is None:
self._cache_macd(mc)
self._previous_candle = candle
return
mp = self._previous_macd
mp2 = self._older_macd
self._evaluate_sell_pattern(mc, mp, mp2)
self._evaluate_buy_pattern(mc, mp, mp2)
self._manage_open_position(candle)
self._cache_macd(mc)
self._previous_candle = candle
def _evaluate_sell_pattern(self, mc, mp, mp2):
ua = float(self.UpperActivation)
ut = float(self.UpperThreshold)
if mc > ua:
self._is_above_upper_activation = True
if self._is_above_upper_activation and mc < mp and mp > mp2 and mp > self._first_upper_peak and not self._first_upper_drop_confirmed:
self._first_upper_peak = mp
if self._first_upper_peak > 0 and mc < ut:
self._first_upper_drop_confirmed = True
if mc < ua:
self._reset_sell_pattern()
return
if self._first_upper_drop_confirmed and mc > ut and mc < mp and mp > mp2 and mp > self._first_upper_peak and mp > self._second_upper_peak and not self._second_upper_drop_confirmed:
self._second_upper_peak = mp
if self._second_upper_peak > 0 and mc < ut:
self._second_upper_drop_confirmed = True
if self._second_upper_drop_confirmed and mc < ut and mp < ut and mp2 < ut and mc < mp and mp > mp2 and mp < self._second_upper_peak:
self._sell_ready = True
if not self._sell_ready:
return
self._enter_short()
def _evaluate_buy_pattern(self, mc, mp, mp2):
la = float(self.LowerActivation)
lt = float(self.LowerThreshold)
if mc < la:
self._is_below_lower_activation = True
if self._is_below_lower_activation and mc > mp and mp < mp2 and mp < self._first_lower_trough and not self._first_lower_rise_confirmed:
self._first_lower_trough = mp
if self._first_lower_trough < 0 and mc > lt:
self._first_lower_rise_confirmed = True
if mc > la:
self._reset_buy_pattern()
return
if self._first_lower_rise_confirmed and mc < lt and mc > mp and mp < mp2 and mp < self._first_lower_trough and mp < self._second_lower_trough and not self._second_lower_rise_confirmed:
self._second_lower_trough = mp
if self._second_lower_trough < 0 and mc > lt:
self._second_lower_rise_confirmed = True
if self._second_lower_rise_confirmed and mc > lt and mp > lt and mp2 > lt and mc > mp and mp < mp2 and mp > self._second_lower_trough:
self._buy_ready = True
if not self._buy_ready:
return
self._enter_long()
def _enter_short(self):
current_pos = self.Position
flatten_vol = float(current_pos) if current_pos > 0 else 0.0
if flatten_vol > 0:
self.SellMarket(flatten_vol)
entry_vol = float(self.Volume) + max(0.0, float(self.Position))
if entry_vol <= 0:
self._reset_sell_pattern()
self._sell_ready = False
return
self.SellMarket(entry_vol)
self._entry_price = float(self._previous_candle.ClosePrice) if self._previous_candle is not None else 0.0
self._initial_short_position = abs(float(self.Position))
self._short_scale_stage = 0
self._long_scale_stage = 0
self._sell_ready = False
self._reset_sell_pattern()
self._reset_buy_pattern()
def _enter_long(self):
current_pos = self.Position
flatten_vol = -float(current_pos) if current_pos < 0 else 0.0
if flatten_vol > 0:
self.BuyMarket(flatten_vol)
entry_vol = float(self.Volume) + max(0.0, -float(self.Position))
if entry_vol <= 0:
self._reset_buy_pattern()
self._buy_ready = False
return
self.BuyMarket(entry_vol)
self._entry_price = float(self._previous_candle.ClosePrice) if self._previous_candle is not None else 0.0
self._initial_long_position = max(0.0, float(self.Position))
self._long_scale_stage = 0
self._short_scale_stage = 0
self._buy_ready = False
self._reset_buy_pattern()
self._reset_sell_pattern()
def _manage_open_position(self, candle):
if self.Position == 0:
self._long_scale_stage = 0
self._short_scale_stage = 0
self._initial_long_position = 0.0
self._initial_short_position = 0.0
return
prev_c = self._previous_candle
if prev_c is None:
return
pt = float(self.ProfitThreshold)
if pt <= 0:
return
unrealized = self._get_unrealized_pnl(candle)
if unrealized < pt:
return
if self.Position > 0:
if self._ema_two_value is not None and float(prev_c.ClosePrice) > self._ema_two_value and self._long_scale_stage == 0:
v = min(float(self.Position), self._initial_long_position / 3.0)
if v > 0:
self.SellMarket(v)
self._long_scale_stage = 1
if self._sma_value is not None and self._ema_four_value is not None and float(prev_c.HighPrice) > (self._sma_value + self._ema_four_value) / 2.0 and self._long_scale_stage == 1:
v = min(float(self.Position), self._initial_long_position / 2.0)
if v > 0:
self.SellMarket(v)
self._long_scale_stage = 2
elif self.Position < 0:
short_pos = -float(self.Position)
if self._ema_two_value is not None and float(prev_c.ClosePrice) < self._ema_two_value and self._short_scale_stage == 0:
v = min(short_pos, self._initial_short_position / 3.0)
if v > 0:
self.BuyMarket(v)
self._short_scale_stage = 1
if self._sma_value is not None and self._ema_four_value is not None and float(prev_c.LowPrice) < (self._sma_value + self._ema_four_value) / 2.0 and self._short_scale_stage == 1:
v = min(short_pos, self._initial_short_position / 2.0)
if v > 0:
self.BuyMarket(v)
self._short_scale_stage = 2
def _cache_macd(self, macd_value):
self._older_macd = self._previous_macd
self._previous_macd = macd_value
def _get_unrealized_pnl(self, candle):
if self.Position == 0 or self._entry_price == 0:
return 0.0
diff = float(candle.ClosePrice) - self._entry_price
return diff * float(self.Position)
def _reset_sell_pattern(self):
self._is_above_upper_activation = False
self._first_upper_drop_confirmed = False
self._second_upper_drop_confirmed = False
self._sell_ready = False
self._first_upper_peak = 0.0
self._second_upper_peak = 0.0
def _reset_buy_pattern(self):
self._is_below_lower_activation = False
self._first_lower_rise_confirmed = False
self._second_lower_rise_confirmed = False
self._buy_ready = False
self._first_lower_trough = 0.0
self._second_lower_trough = 0.0
def CreateClone(self):
return macd_pattern_trader_v03_strategy()