MACD EA 策略
该策略基于 MQL/20010 目录中的 MetaTrader 5 专家顾问 MACD EA (barabashkakvn's edition).mq5,移植到 StockSharp 平台。转换后的版本完整保留了原始 EA 的 MACD 信号、分批止盈以及资金管理逻辑,并采用 StockSharp 的高级 API 实现。
交易思路
- 信号来源:使用可配置的快速、慢速、信号周期计算 MACD 指标。策略比较两根及四根已完成 K 线的 MACD 主线与信号线差值,当差值由负转正时做多,反之做空。
- 持仓管理:入场时设置以点数计的止损和止盈距离。距离会根据标的最小报价步长换算成实际价格;若品种保留 3 或 5 位小数,则将步长额外乘以 10,与原始 EA 的点值调整保持一致。
- 分批止盈:当浮盈达到
PartialProfitPips点时,平掉一半仓位,剩余仓位继续持有。 - 保本逻辑:价格向有利方向移动
BreakevenPips点后,启动保本保护。如果价格回落到开仓价,则立即在开仓价平仓,相当于 EA 将止损移动到成本价。 - 反向信号退出:一旦出现反向的 MACD 交叉,会平掉剩余仓位,避免与指标方向相反的持仓。
资金管理
启用 UseMoneyManagement 后,若连续出现亏损,下一笔交易会增加手数。倍率取决于连续亏损次数(一次亏损后乘以 2,两次亏损后乘以 3,依此类推,最多乘以 7)。最终的下单量等于该倍率与 RiskMultiplier 参数的乘积,模拟原策略的类马丁操作。盈利交易会将亏损计数重置为零。
参数
| 参数 | 说明 |
|---|---|
FastPeriod / SlowPeriod / SignalPeriod |
MACD 指标的周期设置。 |
StopLossPips |
止损距离(点),0 表示不设置。 |
TakeProfitPips |
止盈距离(点),0 表示不设置。 |
PartialProfitPips |
触发分批止盈所需的点数,0 表示不启用。 |
BreakevenPips |
启动保本所需的点数,0 表示不启用。 |
UseMoneyManagement |
是否开启基于连亏的仓位放大。 |
RiskMultiplier |
资金管理激活时使用的额外倍率。 |
BaseVolume |
放大前的基础下单量。 |
CandleType |
用于计算指标的 K 线类型。 |
说明
- 策略通过
SubscribeCandles订阅 K 线并绑定指标,遵循推荐的高级 API 用法。 - 目前仅提供
CS目录下的 C# 实现,暂未提供 Python 版本。 - 按要求未修改或添加任何测试用例。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD crossover strategy converted from the "MACD EA" MetaTrader expert advisor.
/// Implements partial profit taking, breakeven logic, and optional money management scaling.
/// </summary>
public class MacdEaStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _partialProfitPips;
private readonly StrategyParam<int> _breakevenPips;
private readonly StrategyParam<bool> _useMoneyManagement;
private readonly StrategyParam<decimal> _riskMultiplier;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
private readonly List<decimal> _macdDiffs = new();
private decimal? _entryPrice;
private decimal _currentPositionVolume;
private int _entryDirection;
private bool _partialTaken;
private bool _breakevenActive;
private decimal _tradePnl;
private int _consecutiveLosses;
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal moving average period.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Profit target for closing half of the position in pips.
/// </summary>
public int PartialProfitPips
{
get => _partialProfitPips.Value;
set => _partialProfitPips.Value = value;
}
/// <summary>
/// Breakeven activation distance in pips.
/// </summary>
public int BreakevenPips
{
get => _breakevenPips.Value;
set => _breakevenPips.Value = value;
}
/// <summary>
/// Enables money management scaling when true.
/// </summary>
public bool UseMoneyManagement
{
get => _useMoneyManagement.Value;
set => _useMoneyManagement.Value = value;
}
/// <summary>
/// Multiplier applied to the base volume when money management is enabled.
/// </summary>
public decimal RiskMultiplier
{
get => _riskMultiplier.Value;
set => _riskMultiplier.Value = value;
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MacdEaStrategy"/>.
/// </summary>
public MacdEaStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 55)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast moving average period", "Indicators")
.SetOptimize(10, 120, 5);
_slowPeriod = Param(nameof(SlowPeriod), 69)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow moving average period", "Indicators")
.SetOptimize(20, 200, 5);
_signalPeriod = Param(nameof(SignalPeriod), 90)
.SetGreaterThanZero()
.SetDisplay("Signal MA", "Signal moving average period", "Indicators")
.SetOptimize(10, 150, 5);
_stopLossPips = Param(nameof(StopLossPips), 80)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
.SetOptimize(0, 200, 10);
_takeProfitPips = Param(nameof(TakeProfitPips), 500)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk")
.SetOptimize(0, 800, 20);
_partialProfitPips = Param(nameof(PartialProfitPips), 70)
.SetNotNegative()
.SetDisplay("Partial Profit", "Pips to close half the position", "Risk")
.SetOptimize(0, 200, 10);
_breakevenPips = Param(nameof(BreakevenPips), 0)
.SetNotNegative()
.SetDisplay("Breakeven", "Distance to activate breakeven", "Risk")
.SetOptimize(0, 200, 10);
_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
.SetDisplay("Use MM", "Enable money management scaling", "Money Management");
_riskMultiplier = Param(nameof(RiskMultiplier), 1m)
.SetGreaterThanZero()
.SetDisplay("Risk Multiplier", "Multiplier applied to base volume", "Money Management")
.SetOptimize(0.5m, 5m, 0.5m);
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Default order size", "General")
.SetOptimize(0.1m, 5m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdDiffs.Clear();
_entryPrice = null;
_currentPositionVolume = 0m;
_entryDirection = 0;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 0m;
_consecutiveLosses = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = BaseVolume;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = FastPeriod }, LongMa = { Length = SlowPeriod } },
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _macd.Process(candle);
if (!_macd.IsFormed)
return;
var macdValue = result as MovingAverageConvergenceDivergenceSignalValue;
if (macdValue == null)
return;
var macdLine = macdValue.Macd ?? 0m;
var signalLine = macdValue.Signal ?? 0m;
var diff = macdLine - signalLine;
_macdDiffs.Add(diff);
if (_macdDiffs.Count > 50)
_macdDiffs.RemoveRange(0, _macdDiffs.Count - 50);
if (_macdDiffs.Count < 5)
return;
var diffTwo = _macdDiffs[^3];
var diffFour = _macdDiffs[^5];
var bullish = diffTwo > 0m && diffFour < 0m;
var bearish = diffTwo < 0m && diffFour > 0m;
var pip = GetPipSize();
if (Position > 0m)
{
if (HandleLongPosition(candle, bearish, pip))
return;
}
else if (Position < 0m)
{
if (HandleShortPosition(candle, bullish, pip))
return;
}
if (Position != 0m)
return;
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
if (bullish)
{
BuyMarket(volume);
InitializeTradeState(candle.ClosePrice, volume, 1);
}
else if (bearish)
{
SellMarket(volume);
InitializeTradeState(candle.ClosePrice, volume, -1);
}
}
private bool HandleLongPosition(ICandleMessage candle, bool bearishSignal, decimal pip)
{
if (_entryPrice is not decimal entry)
return false;
var remainingVolume = _currentPositionVolume > 0m ? _currentPositionVolume : Math.Abs(Position);
remainingVolume = NormalizeVolume(remainingVolume);
if (remainingVolume <= 0m)
return false;
var stop = StopLossPips > 0 ? entry - StopLossPips * pip : (decimal?)null;
var take = TakeProfitPips > 0 ? entry + TakeProfitPips * pip : (decimal?)null;
var partial = PartialProfitPips > 0 ? entry + PartialProfitPips * pip : (decimal?)null;
var breakeven = BreakevenPips > 0 ? entry + BreakevenPips * pip : (decimal?)null;
if (stop is decimal stopPrice && candle.LowPrice <= stopPrice)
{
CloseLong(remainingVolume, stopPrice);
return true;
}
if (take is decimal takePrice && candle.HighPrice >= takePrice)
{
CloseLong(remainingVolume, takePrice);
return true;
}
if (!_partialTaken && partial is decimal partialPrice && candle.HighPrice >= partialPrice)
{
var halfVolume = NormalizeVolume(remainingVolume / 2m);
if (halfVolume > 0m)
{
SellMarket(halfVolume);
RegisterPnl(partialPrice, halfVolume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - halfVolume);
_partialTaken = true;
return true;
}
}
if (breakeven is decimal breakevenPrice && !_breakevenActive && candle.HighPrice >= breakevenPrice)
_breakevenActive = true;
if (_breakevenActive && candle.LowPrice <= entry)
{
CloseLong(remainingVolume, entry);
return true;
}
if (bearishSignal)
{
CloseLong(remainingVolume, candle.ClosePrice);
return true;
}
return false;
}
private bool HandleShortPosition(ICandleMessage candle, bool bullishSignal, decimal pip)
{
if (_entryPrice is not decimal entry)
return false;
var remainingVolume = _currentPositionVolume > 0m ? _currentPositionVolume : Math.Abs(Position);
remainingVolume = NormalizeVolume(remainingVolume);
if (remainingVolume <= 0m)
return false;
var stop = StopLossPips > 0 ? entry + StopLossPips * pip : (decimal?)null;
var take = TakeProfitPips > 0 ? entry - TakeProfitPips * pip : (decimal?)null;
var partial = PartialProfitPips > 0 ? entry - PartialProfitPips * pip : (decimal?)null;
var breakeven = BreakevenPips > 0 ? entry - BreakevenPips * pip : (decimal?)null;
if (stop is decimal stopPrice && candle.HighPrice >= stopPrice)
{
CloseShort(remainingVolume, stopPrice);
return true;
}
if (take is decimal takePrice && candle.LowPrice <= takePrice)
{
CloseShort(remainingVolume, takePrice);
return true;
}
if (!_partialTaken && partial is decimal partialPrice && candle.LowPrice <= partialPrice)
{
var halfVolume = NormalizeVolume(remainingVolume / 2m);
if (halfVolume > 0m)
{
BuyMarket(halfVolume);
RegisterPnl(partialPrice, halfVolume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - halfVolume);
_partialTaken = true;
return true;
}
}
if (breakeven is decimal breakevenPrice && !_breakevenActive && candle.LowPrice <= breakevenPrice)
_breakevenActive = true;
if (_breakevenActive && candle.HighPrice >= entry)
{
CloseShort(remainingVolume, entry);
return true;
}
if (bullishSignal)
{
CloseShort(remainingVolume, candle.ClosePrice);
return true;
}
return false;
}
private void CloseLong(decimal volume, decimal exitPrice)
{
volume = NormalizeVolume(volume);
if (volume <= 0m)
return;
SellMarket(volume);
RegisterPnl(exitPrice, volume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - volume);
FinalizeTradeIfClosed();
}
private void CloseShort(decimal volume, decimal exitPrice)
{
volume = NormalizeVolume(volume);
if (volume <= 0m)
return;
BuyMarket(volume);
RegisterPnl(exitPrice, volume);
_currentPositionVolume = Math.Max(0m, _currentPositionVolume - volume);
FinalizeTradeIfClosed();
}
private void InitializeTradeState(decimal entryPrice, decimal volume, int direction)
{
_entryPrice = entryPrice;
_currentPositionVolume = NormalizeVolume(Math.Abs(volume));
_entryDirection = direction;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 0m;
}
private decimal CalculateOrderVolume()
{
var volume = BaseVolume;
if (UseMoneyManagement)
{
var multiplier = _consecutiveLosses switch
{
0 => 1m,
1 => 2m,
2 => 3m,
3 => 4m,
4 => 5m,
5 => 6m,
_ => 7m,
};
volume *= multiplier * RiskMultiplier;
}
return NormalizeVolume(volume);
}
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);
volume = steps * step;
if (volume < step)
return 0m;
return volume;
}
private decimal GetPipSize()
{
var sec = Security;
var step = sec?.PriceStep ?? 1m;
if (step <= 0m)
return 1m;
var tmp = step;
var decimals = 0;
while (decimals < 10 && decimal.Truncate(tmp) != tmp)
{
tmp *= 10m;
decimals++;
}
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private void RegisterPnl(decimal exitPrice, decimal volume)
{
if (_entryPrice is not decimal entry || _entryDirection == 0)
return;
var pnl = (exitPrice - entry) * volume * _entryDirection;
_tradePnl += pnl;
}
private void FinalizeTradeIfClosed()
{
if (_currentPositionVolume > 0m)
return;
if (_tradePnl > 0m)
_consecutiveLosses = 0;
else if (_tradePnl < 0m)
_consecutiveLosses++;
else
_consecutiveLosses = 0;
ResetTradeState();
}
private void ResetTradeState()
{
_entryPrice = null;
_currentPositionVolume = 0m;
_entryDirection = 0;
_partialTaken = false;
_breakevenActive = false;
_tradePnl = 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.Indicators import MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_ea_strategy(Strategy):
def __init__(self):
super(macd_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 55)
self._slow_period = self.Param("SlowPeriod", 69)
self._signal_period = self.Param("SignalPeriod", 90)
self._stop_loss_pips = self.Param("StopLossPips", 80)
self._take_profit_pips = self.Param("TakeProfitPips", 500)
self._partial_profit_pips = self.Param("PartialProfitPips", 70)
self._breakeven_pips = self.Param("BreakevenPips", 0)
self._use_money_management = self.Param("UseMoneyManagement", False)
self._risk_multiplier = self.Param("RiskMultiplier", 1.0)
self._base_volume = self.Param("BaseVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._macd = None
self._macd_diffs = []
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
self._consecutive_losses = 0
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def PartialProfitPips(self):
return self._partial_profit_pips.Value
@property
def BreakevenPips(self):
return self._breakeven_pips.Value
@property
def UseMoneyManagement(self):
return self._use_money_management.Value
@property
def RiskMultiplier(self):
return self._risk_multiplier.Value
@property
def BaseVolume(self):
return self._base_volume.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_ea_strategy, self).OnStarted2(time)
self.Volume = float(self.BaseVolume)
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = self.FastPeriod
self._macd.Macd.LongMa.Length = self.SlowPeriod
self._macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._macd, candle)
civ.IsFinal = True
result = self._macd.Process(civ)
if not self._macd.IsFormed:
return
try:
macd_line = float(result.Macd) if result.Macd is not None else 0.0
signal_line = float(result.Signal) if result.Signal is not None else 0.0
except:
return
diff = macd_line - signal_line
self._macd_diffs.append(diff)
if len(self._macd_diffs) > 50:
self._macd_diffs = self._macd_diffs[-50:]
if len(self._macd_diffs) < 5:
return
diff_two = self._macd_diffs[-3]
diff_four = self._macd_diffs[-5]
bullish = diff_two > 0 and diff_four < 0
bearish = diff_two < 0 and diff_four > 0
pip = self._get_pip_size()
pos = float(self.Position)
if pos > 0:
if self._handle_long_position(candle, bearish, pip):
return
elif pos < 0:
if self._handle_short_position(candle, bullish, pip):
return
if float(self.Position) != 0:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
if bullish:
self.BuyMarket(volume)
self._init_trade_state(float(candle.ClosePrice), volume, 1)
elif bearish:
self.SellMarket(volume)
self._init_trade_state(float(candle.ClosePrice), volume, -1)
def _handle_long_position(self, candle, bearish_signal, pip):
if self._entry_price is None:
return False
entry = self._entry_price
remaining = self._current_position_volume if self._current_position_volume > 0 else abs(float(self.Position))
if remaining <= 0:
return False
stop = entry - self.StopLossPips * pip if self.StopLossPips > 0 else None
take = entry + self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
partial = entry + self.PartialProfitPips * pip if self.PartialProfitPips > 0 else None
breakeven = entry + self.BreakevenPips * pip if self.BreakevenPips > 0 else None
if stop is not None and float(candle.LowPrice) <= stop:
self.SellMarket(remaining)
self._register_pnl(stop, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if take is not None and float(candle.HighPrice) >= take:
self.SellMarket(remaining)
self._register_pnl(take, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if not self._partial_taken and partial is not None and float(candle.HighPrice) >= partial:
half = remaining / 2.0
if half > 0:
self.SellMarket(half)
self._register_pnl(partial, half)
self._current_position_volume = max(0.0, self._current_position_volume - half)
self._partial_taken = True
return True
if breakeven is not None and not self._breakeven_active and float(candle.HighPrice) >= breakeven:
self._breakeven_active = True
if self._breakeven_active and float(candle.LowPrice) <= entry:
self.SellMarket(remaining)
self._register_pnl(entry, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if bearish_signal:
self.SellMarket(remaining)
self._register_pnl(float(candle.ClosePrice), remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
return False
def _handle_short_position(self, candle, bullish_signal, pip):
if self._entry_price is None:
return False
entry = self._entry_price
remaining = self._current_position_volume if self._current_position_volume > 0 else abs(float(self.Position))
if remaining <= 0:
return False
stop = entry + self.StopLossPips * pip if self.StopLossPips > 0 else None
take = entry - self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
partial = entry - self.PartialProfitPips * pip if self.PartialProfitPips > 0 else None
breakeven = entry - self.BreakevenPips * pip if self.BreakevenPips > 0 else None
if stop is not None and float(candle.HighPrice) >= stop:
self.BuyMarket(remaining)
self._register_pnl(stop, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if take is not None and float(candle.LowPrice) <= take:
self.BuyMarket(remaining)
self._register_pnl(take, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if not self._partial_taken and partial is not None and float(candle.LowPrice) <= partial:
half = remaining / 2.0
if half > 0:
self.BuyMarket(half)
self._register_pnl(partial, half)
self._current_position_volume = max(0.0, self._current_position_volume - half)
self._partial_taken = True
return True
if breakeven is not None and not self._breakeven_active and float(candle.LowPrice) <= breakeven:
self._breakeven_active = True
if self._breakeven_active and float(candle.HighPrice) >= entry:
self.BuyMarket(remaining)
self._register_pnl(entry, remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
if bullish_signal:
self.BuyMarket(remaining)
self._register_pnl(float(candle.ClosePrice), remaining)
self._current_position_volume = 0.0
self._finalize_trade()
return True
return False
def _init_trade_state(self, entry_price, volume, direction):
self._entry_price = entry_price
self._current_position_volume = abs(volume)
self._entry_direction = direction
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
def _calculate_order_volume(self):
volume = float(self.BaseVolume)
if self.UseMoneyManagement:
losses = self._consecutive_losses
if losses == 0:
mult = 1.0
elif losses == 1:
mult = 2.0
elif losses == 2:
mult = 3.0
elif losses == 3:
mult = 4.0
elif losses == 4:
mult = 5.0
elif losses == 5:
mult = 6.0
else:
mult = 7.0
volume *= mult * float(self.RiskMultiplier)
return volume
def _get_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0:
return 1.0
tmp = step
decimals = 0
while decimals < 10 and int(tmp) != tmp:
tmp *= 10.0
decimals += 1
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def _register_pnl(self, exit_price, volume):
if self._entry_price is None or self._entry_direction == 0:
return
pnl = (exit_price - self._entry_price) * volume * self._entry_direction
self._trade_pnl += pnl
def _finalize_trade(self):
if self._current_position_volume > 0:
return
if self._trade_pnl > 0:
self._consecutive_losses = 0
elif self._trade_pnl < 0:
self._consecutive_losses += 1
else:
self._consecutive_losses = 0
self._reset_trade_state()
def _reset_trade_state(self):
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
def OnReseted(self):
super(macd_ea_strategy, self).OnReseted()
self._macd_diffs = []
self._entry_price = None
self._current_position_volume = 0.0
self._entry_direction = 0
self._partial_taken = False
self._breakeven_active = False
self._trade_pnl = 0.0
self._consecutive_losses = 0
def CreateClone(self):
return macd_ea_strategy()