Dealers Trade MACD 策略
本策略移植自 MQL5 的 “Dealers Trade v7.74 MACD” 专家顾问。它是一套顺势加仓系统,通过 MACD 主线的斜率决定多空方向,并在价格沿趋势推进时逐步扩大仓位。作者建议用于 H4、D1 等较高周期,以过滤短周期噪音。
策略逻辑
- 信号判定:订阅所选周期的蜡烛图,在每根收盘 K 线计算 MACD 主线数值。主线向上视为看多,向下视为看空。参数
ReverseCondition可以反转信号,用于需要反向交易的账户。 - 仓位控制:第一笔订单使用固定手数
FixedVolume。若该值为 0,则改为使用账户当前权益 *RiskPercent(百分比)÷ 止损距离的方式动态确定手数。后续加仓单的体积按照VolumeMultiplier^(当前仓位数量)递增,同时要求价格距离上一次成交至少IntervalPoints * PriceStep。若加仓后仓位数量超过MaxPositions或总量超过MaxVolume,则放弃该信号。 - 仓位管理:每笔持仓都记录独立的止损、止盈价格,根据参数
StopLossPoints、TakeProfitPoints计算(单位为最小报价步长)。当TrailingStopPoints大于 0 时,在浮盈超过TrailingStopPoints + TrailingStepPoints后启动追踪止损,模拟原始 EA 的移动保护逻辑。 - 账户保护:当持仓数量大于
PositionsForProtection且浮盈合计达到SecureProfit时,策略会先平掉盈利最高的那笔仓位锁定收益,然后再考虑继续加仓。
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
CandleType |
H4 | 计算信号的蜡烛周期。 |
FixedVolume |
0.1 | 第一笔订单的手数。设为 0 时启用按风险百分比的动态手数。 |
RiskPercent |
5 | 当 FixedVolume = 0 时,允许冒的权益百分比。 |
StopLossPoints |
90 | 止损距离(按价格最小变动单位计)。0 表示不下止损。 |
TakeProfitPoints |
30 | 止盈距离(价格步长单位)。0 表示不设定。 |
TrailingStopPoints |
15 | 移动止损的基础距离。0 表示关闭追踪。 |
TrailingStepPoints |
5 | 每次更新移动止损前,额外需要的利润空间。 |
MaxPositions |
5 | 最多允许的加仓次数。 |
IntervalPoints |
15 | 相邻加仓所需的最小价格间隔(单位:价格步长)。 |
SecureProfit |
50 | 触发账户保护的浮盈阈值(报价货币)。 |
AccountProtection |
true | 是否启用账户保护机制。 |
PositionsForProtection |
3 | 账户保护生效所需的最少持仓数量。 |
ReverseCondition |
false | 是否反转 MACD 方向判断。 |
MacdFastPeriod |
14 | MACD 快速 EMA 周期。 |
MacdSlowPeriod |
26 | MACD 慢速 EMA 周期。 |
MacdSignalPeriod |
1 | MACD 信号 EMA 周期(与原始 EA 相同)。 |
MaxVolume |
5 | 累计仓位数量上限。 |
VolumeMultiplier |
1.6 | 每次加仓的手数倍增系数。 |
注意事项
- MQL 版本允许同时持有多头与空头(对冲模式)。StockSharp 默认使用净额持仓,因此本移植版在反向开仓前会先平掉相反方向的仓位。
- 策略只在蜡烛收盘后评估 MACD,因此不会像逐笔行情那样对瞬时波动做出反应,但更适合历史测试与实盘验证。
- 所有“点数”参数都会乘以交易品种的
PriceStep。若品种没有提供该信息,则退回到 0.0001 的默认步长,请在必要时调整参数。 - 当
FixedVolume = 0且未设置止损距离时,无法计算风险,策略会跳过交易。
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>
/// Dealers Trade MACD strategy converted from MQL5 implementation.
/// </summary>
public class DealersTradeMacdStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _intervalPoints;
private readonly StrategyParam<decimal> _secureProfit;
private readonly StrategyParam<bool> _accountProtection;
private readonly StrategyParam<int> _positionsForProtection;
private readonly StrategyParam<bool> _reverseCondition;
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _volumeMultiplier;
private MovingAverageConvergenceDivergence _macd = null!;
private decimal? _previousMacd;
private decimal _lastEntryPrice;
private int _cooldown;
private readonly List<PositionState> _longPositions = new();
private readonly List<PositionState> _shortPositions = new();
/// <summary>
/// Initializes a new instance of <see cref="DealersTradeMacdStrategy"/>.
/// </summary>
public DealersTradeMacdStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_fixedVolume = Param(nameof(FixedVolume), 0.1m)
.SetDisplay("Fixed Volume", "Lot size used when above zero", "Risk");
_riskPercent = Param(nameof(RiskPercent), 5m)
.SetDisplay("Risk %", "Risk percent when fixed volume is zero", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 90m)
.SetDisplay("Stop Loss pts", "Stop loss distance in price steps", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 30m)
.SetDisplay("Take Profit pts", "Take profit distance in price steps", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
.SetDisplay("Trailing Stop pts", "Trailing stop distance in price steps", "Risk");
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
.SetDisplay("Trailing Step pts", "Additional distance before trailing updates", "Risk");
_maxPositions = Param(nameof(MaxPositions), 2)
.SetDisplay("Max Positions", "Maximum concurrent entries", "Money Management");
_intervalPoints = Param(nameof(IntervalPoints), 50m)
.SetDisplay("Interval pts", "Minimum distance between new entries", "Money Management");
_secureProfit = Param(nameof(SecureProfit), 50m)
.SetDisplay("Secure Profit", "Profit threshold that triggers protection", "Money Management");
_accountProtection = Param(nameof(AccountProtection), true)
.SetDisplay("Account Protection", "Close best trade after reaching secure profit", "Money Management");
_positionsForProtection = Param(nameof(PositionsForProtection), 3)
.SetDisplay("Protect From", "Minimum positions before triggering protection", "Money Management");
_reverseCondition = Param(nameof(ReverseCondition), false)
.SetDisplay("Reverse Signal", "Invert MACD slope direction", "Trading");
_macdFastPeriod = Param(nameof(MacdFastPeriod), 14)
.SetDisplay("MACD Fast", "Fast EMA period", "Indicators");
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
.SetDisplay("MACD Slow", "Slow EMA period", "Indicators");
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 1)
.SetDisplay("MACD Signal", "Signal EMA period", "Indicators");
_maxVolume = Param(nameof(MaxVolume), 5m)
.SetDisplay("Max Volume", "Absolute cap for trade volume", "Risk")
.SetGreaterThanZero();
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.6m)
.SetDisplay("Volume Multiplier", "Multiplier for additional positions", "Money Management")
.SetGreaterThanZero();
}
/// <summary>
/// Candle type used for signal calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fixed lot size. When zero risk based sizing is used.
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Percent of equity risked when sizing dynamically.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Extra distance required before the trailing stop moves.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Maximum number of open entries.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Minimum price distance between sequential entries.
/// </summary>
public decimal IntervalPoints
{
get => _intervalPoints.Value;
set => _intervalPoints.Value = value;
}
/// <summary>
/// Profit target for account protection logic.
/// </summary>
public decimal SecureProfit
{
get => _secureProfit.Value;
set => _secureProfit.Value = value;
}
/// <summary>
/// Enables profit locking when enough trades are open.
/// </summary>
public bool AccountProtection
{
get => _accountProtection.Value;
set => _accountProtection.Value = value;
}
/// <summary>
/// Minimum number of positions before account protection activates.
/// </summary>
public int PositionsForProtection
{
get => _positionsForProtection.Value;
set => _positionsForProtection.Value = value;
}
/// <summary>
/// Inverts the MACD slope direction.
/// </summary>
public bool ReverseCondition
{
get => _reverseCondition.Value;
set => _reverseCondition.Value = value;
}
/// <summary>
/// MACD fast EMA period.
/// </summary>
public int MacdFastPeriod
{
get => _macdFastPeriod.Value;
set => _macdFastPeriod.Value = value;
}
/// <summary>
/// MACD slow EMA period.
/// </summary>
public int MacdSlowPeriod
{
get => _macdSlowPeriod.Value;
set => _macdSlowPeriod.Value = value;
}
/// <summary>
/// MACD signal EMA period.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Maximum allowed total volume.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Multiplier applied to the base volume for each additional entry.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd?.Reset();
_previousMacd = null;
_lastEntryPrice = 0m;
_cooldown = 0;
_longPositions.Clear();
_shortPositions.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergence(
new ExponentialMovingAverage { Length = MacdSlowPeriod },
new ExponentialMovingAverage { Length = MacdFastPeriod }
);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_macd, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal macdValue)
{
if (candle.State != CandleStates.Finished)
return;
HandleTrailingAndExits(candle);
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousMacd = macdValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_previousMacd = macdValue;
return;
}
var openPositions = _longPositions.Count + _shortPositions.Count;
var continueOpening = openPositions < MaxPositions;
var direction = 0;
if (_previousMacd is null)
{
_previousMacd = macdValue;
return;
}
if (macdValue > _previousMacd)
direction = 1;
else if (macdValue < _previousMacd)
direction = -1;
if (ReverseCondition)
direction = -direction;
if (AccountProtection && openPositions > PositionsForProtection)
{
var totalProfit = CalculateTotalProfit(candle.ClosePrice);
if (totalProfit >= SecureProfit)
{
CloseMostProfitablePosition(candle.ClosePrice);
_previousMacd = macdValue;
return;
}
}
if (continueOpening && direction > 0 && _shortPositions.Count == 0)
TryOpenLong(candle);
else if (continueOpening && direction < 0 && _longPositions.Count == 0)
TryOpenShort(candle);
_previousMacd = macdValue;
}
private void HandleTrailingAndExits(ICandleMessage candle)
{
var step = GetPriceStep();
var trailingDistance = TrailingStopPoints * step;
var trailingActivation = (TrailingStopPoints + TrailingStepPoints) * step;
// Collect exits first, then execute to avoid collection modification during enumeration
var longExits = new List<PositionState>();
var longSnapshot = _longPositions.ToList();
foreach (var state in longSnapshot)
{
if (state.TakeProfitPrice > 0 && candle.HighPrice >= state.TakeProfitPrice)
{
longExits.Add(state);
continue;
}
if (state.StopPrice > 0 && candle.LowPrice <= state.StopPrice)
{
longExits.Add(state);
continue;
}
if (TrailingStopPoints > 0 && candle.ClosePrice - state.EntryPrice > trailingActivation)
{
var candidateStop = candle.ClosePrice - trailingDistance;
if (state.StopPrice == 0m || state.StopPrice < candle.ClosePrice - trailingActivation)
state.StopPrice = candidateStop;
}
}
foreach (var state in longExits)
{
Volume = state.Volume;
SellMarket();
_longPositions.Remove(state);
_lastEntryPrice = 0m;
}
var shortExits = new List<PositionState>();
var shortSnapshot = _shortPositions.ToList();
foreach (var state in shortSnapshot)
{
if (state.TakeProfitPrice > 0 && candle.LowPrice <= state.TakeProfitPrice)
{
shortExits.Add(state);
continue;
}
if (state.StopPrice > 0 && candle.HighPrice >= state.StopPrice)
{
shortExits.Add(state);
continue;
}
if (TrailingStopPoints > 0 && state.EntryPrice - candle.ClosePrice > trailingActivation)
{
var candidateStop = candle.ClosePrice + trailingDistance;
if (state.StopPrice == 0m || state.StopPrice > candle.ClosePrice + trailingActivation)
state.StopPrice = candidateStop;
}
}
foreach (var state in shortExits)
{
Volume = state.Volume;
BuyMarket();
_shortPositions.Remove(state);
_lastEntryPrice = 0m;
}
}
private void TryOpenLong(ICandleMessage candle)
{
var step = GetPriceStep();
var interval = IntervalPoints * step;
if (_lastEntryPrice != 0m && Math.Abs(_lastEntryPrice - candle.ClosePrice) < interval)
return;
var baseVolume = FixedVolume > 0 ? FixedVolume : CalculateRiskVolume(step);
if (baseVolume <= 0)
return;
var openPositions = _longPositions.Count + _shortPositions.Count;
var lotCoefficient = openPositions == 0 ? 1m : Pow(VolumeMultiplier, openPositions + 1);
var volume = NormalizeVolume(baseVolume * lotCoefficient);
if (volume <= 0 || volume > MaxVolume)
return;
var stopDistance = StopLossPoints * step;
var takeDistance = TakeProfitPoints * step;
Volume = volume;
BuyMarket();
_longPositions.Add(new PositionState
{
EntryPrice = candle.ClosePrice,
Volume = volume,
StopPrice = stopDistance > 0 ? candle.ClosePrice - stopDistance : 0m,
TakeProfitPrice = takeDistance > 0 ? candle.ClosePrice + takeDistance : 0m
});
_lastEntryPrice = candle.ClosePrice;
_cooldown = 3;
}
private void TryOpenShort(ICandleMessage candle)
{
var step = GetPriceStep();
var interval = IntervalPoints * step;
if (_lastEntryPrice != 0m && Math.Abs(_lastEntryPrice - candle.ClosePrice) < interval)
return;
var baseVolume = FixedVolume > 0 ? FixedVolume : CalculateRiskVolume(step);
if (baseVolume <= 0)
return;
var openPositions = _longPositions.Count + _shortPositions.Count;
var lotCoefficient = openPositions == 0 ? 1m : Pow(VolumeMultiplier, openPositions + 1);
var volume = NormalizeVolume(baseVolume * lotCoefficient);
if (volume <= 0 || volume > MaxVolume)
return;
var stopDistance = StopLossPoints * step;
var takeDistance = TakeProfitPoints * step;
Volume = volume;
SellMarket();
_shortPositions.Add(new PositionState
{
EntryPrice = candle.ClosePrice,
Volume = volume,
StopPrice = stopDistance > 0 ? candle.ClosePrice + stopDistance : 0m,
TakeProfitPrice = takeDistance > 0 ? candle.ClosePrice - takeDistance : 0m
});
_lastEntryPrice = candle.ClosePrice;
_cooldown = 3;
}
private decimal CalculateRiskVolume(decimal priceStep)
{
if (StopLossPoints <= 0)
return 0m;
var stopDistance = StopLossPoints * priceStep;
if (stopDistance <= 0)
return 0m;
if (Portfolio is null)
return 0m;
var equity = Portfolio.CurrentValue ?? 0m;
if (equity <= 0)
return 0m;
var riskAmount = equity * (RiskPercent / 100m);
return riskAmount / stopDistance;
}
private decimal CalculateTotalProfit(decimal currentPrice)
{
decimal profit = 0m;
foreach (var pos in _longPositions)
profit += (currentPrice - pos.EntryPrice) * pos.Volume;
foreach (var pos in _shortPositions)
profit += (pos.EntryPrice - currentPrice) * pos.Volume;
return profit;
}
private void CloseMostProfitablePosition(decimal currentPrice)
{
PositionState best = null;
var bestIsLong = false;
decimal bestProfit = 0m;
foreach (var pos in _longPositions)
{
var profit = (currentPrice - pos.EntryPrice) * pos.Volume;
if (profit > bestProfit)
{
bestProfit = profit;
best = pos;
bestIsLong = true;
}
}
foreach (var pos in _shortPositions)
{
var profit = (pos.EntryPrice - currentPrice) * pos.Volume;
if (profit > bestProfit)
{
bestProfit = profit;
best = pos;
bestIsLong = false;
}
}
if (best is null || bestProfit <= 0m)
return;
if (bestIsLong)
{
SellMarket();
_longPositions.Remove(best);
}
else
{
BuyMarket();
_shortPositions.Remove(best);
}
_lastEntryPrice = 0m;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0)
{
var steps = Math.Floor(volume / step);
volume = steps * step;
}
return volume;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep ?? 0m;
if (step > 0)
return step;
var decimals = Security?.Decimals ?? 0;
if (decimals > 0)
return (decimal)Math.Pow(10, -decimals);
return 0.0001m;
}
private static decimal Pow(decimal value, int power)
{
if (power <= 0)
return 1m;
return (decimal)Math.Pow((double)value, power);
}
private sealed class PositionState
{
public decimal EntryPrice { get; set; }
public decimal Volume { get; set; }
public decimal StopPrice { get; set; }
public decimal TakeProfitPrice { get; set; }
}
}
import clr
import math
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 dealers_trade_macd_strategy(Strategy):
def __init__(self):
super(dealers_trade_macd_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._fixed_volume = self.Param("FixedVolume", 0.1)
self._risk_percent = self.Param("RiskPercent", 5.0)
self._stop_loss_points = self.Param("StopLossPoints", 90.0)
self._take_profit_points = self.Param("TakeProfitPoints", 30.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 15.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0)
self._max_positions = self.Param("MaxPositions", 2)
self._interval_points = self.Param("IntervalPoints", 50.0)
self._secure_profit = self.Param("SecureProfit", 50.0)
self._account_protection = self.Param("AccountProtection", True)
self._positions_for_protection = self.Param("PositionsForProtection", 3)
self._reverse_condition = self.Param("ReverseCondition", False)
self._macd_fast_period = self.Param("MacdFastPeriod", 14)
self._macd_slow_period = self.Param("MacdSlowPeriod", 26)
self._macd_signal_period = self.Param("MacdSignalPeriod", 1)
self._max_volume = self.Param("MaxVolume", 5.0)
self._volume_multiplier = self.Param("VolumeMultiplier", 1.6)
self._macd = None
self._previous_macd = None
self._last_entry_price = 0.0
self._cooldown = 0
self._long_positions = []
self._short_positions = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FixedVolume(self):
return self._fixed_volume.Value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def TrailingStepPoints(self):
return self._trailing_step_points.Value
@property
def MaxPositions(self):
return self._max_positions.Value
@property
def IntervalPoints(self):
return self._interval_points.Value
@property
def SecureProfit(self):
return self._secure_profit.Value
@property
def AccountProtection(self):
return self._account_protection.Value
@property
def PositionsForProtection(self):
return self._positions_for_protection.Value
@property
def ReverseCondition(self):
return self._reverse_condition.Value
@property
def MacdFastPeriod(self):
return self._macd_fast_period.Value
@property
def MacdSlowPeriod(self):
return self._macd_slow_period.Value
@property
def MacdSignalPeriod(self):
return self._macd_signal_period.Value
@property
def MaxVolume(self):
return self._max_volume.Value
@property
def VolumeMultiplier(self):
return self._volume_multiplier.Value
def OnStarted2(self, time):
super(dealers_trade_macd_strategy, self).OnStarted2(time)
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.MacdSlowPeriod
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.MacdFastPeriod
self._macd = MovingAverageConvergenceDivergence(slow_ema, fast_ema)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._macd, self._process_candle).Start()
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
self._handle_trailing_and_exits(candle)
if not self.IsFormedAndOnlineAndAllowTrading():
self._previous_macd = macd_value
return
if self._cooldown > 0:
self._cooldown -= 1
self._previous_macd = macd_value
return
open_positions = len(self._long_positions) + len(self._short_positions)
continue_opening = open_positions < self.MaxPositions
direction = 0
if self._previous_macd is None:
self._previous_macd = macd_value
return
if macd_value > self._previous_macd:
direction = 1
elif macd_value < self._previous_macd:
direction = -1
if self.ReverseCondition:
direction = -direction
if self.AccountProtection and open_positions > self.PositionsForProtection:
total_profit = self._calc_total_profit(float(candle.ClosePrice))
if total_profit >= self.SecureProfit:
self._close_most_profitable(float(candle.ClosePrice))
self._previous_macd = macd_value
return
if continue_opening and direction > 0 and len(self._short_positions) == 0:
self._try_open_long(candle)
elif continue_opening and direction < 0 and len(self._long_positions) == 0:
self._try_open_short(candle)
self._previous_macd = macd_value
def _handle_trailing_and_exits(self, candle):
step = self._get_price_step()
trail_dist = self.TrailingStopPoints * step
trail_act = (self.TrailingStopPoints + self.TrailingStepPoints) * step
long_exits = []
for s in list(self._long_positions):
if s["take"] > 0 and float(candle.HighPrice) >= s["take"]:
long_exits.append(s); continue
if s["stop"] > 0 and float(candle.LowPrice) <= s["stop"]:
long_exits.append(s); continue
if self.TrailingStopPoints > 0 and float(candle.ClosePrice) - s["entry"] > trail_act:
cs = float(candle.ClosePrice) - trail_dist
if s["stop"] == 0 or s["stop"] < float(candle.ClosePrice) - trail_act:
s["stop"] = cs
for s in long_exits:
self.Volume = s["volume"]
self.SellMarket()
self._long_positions.remove(s)
self._last_entry_price = 0.0
short_exits = []
for s in list(self._short_positions):
if s["take"] > 0 and float(candle.LowPrice) <= s["take"]:
short_exits.append(s); continue
if s["stop"] > 0 and float(candle.HighPrice) >= s["stop"]:
short_exits.append(s); continue
if self.TrailingStopPoints > 0 and s["entry"] - float(candle.ClosePrice) > trail_act:
cs = float(candle.ClosePrice) + trail_dist
if s["stop"] == 0 or s["stop"] > float(candle.ClosePrice) + trail_act:
s["stop"] = cs
for s in short_exits:
self.Volume = s["volume"]
self.BuyMarket()
self._short_positions.remove(s)
self._last_entry_price = 0.0
def _try_open_long(self, candle):
step = self._get_price_step()
interval = self.IntervalPoints * step
if self._last_entry_price != 0 and abs(self._last_entry_price - float(candle.ClosePrice)) < interval:
return
base_vol = self.FixedVolume if self.FixedVolume > 0 else self._calc_risk_vol(step)
if base_vol <= 0:
return
n = len(self._long_positions) + len(self._short_positions)
coeff = 1.0 if n == 0 else math.pow(self.VolumeMultiplier, n + 1)
vol = self._norm_vol(base_vol * coeff)
if vol <= 0 or vol > self.MaxVolume:
return
sd = self.StopLossPoints * step
td = self.TakeProfitPoints * step
self.Volume = vol
self.BuyMarket()
self._long_positions.append({
"entry": float(candle.ClosePrice), "volume": vol,
"stop": float(candle.ClosePrice) - sd if sd > 0 else 0,
"take": float(candle.ClosePrice) + td if td > 0 else 0
})
self._last_entry_price = float(candle.ClosePrice)
self._cooldown = 3
def _try_open_short(self, candle):
step = self._get_price_step()
interval = self.IntervalPoints * step
if self._last_entry_price != 0 and abs(self._last_entry_price - float(candle.ClosePrice)) < interval:
return
base_vol = self.FixedVolume if self.FixedVolume > 0 else self._calc_risk_vol(step)
if base_vol <= 0:
return
n = len(self._long_positions) + len(self._short_positions)
coeff = 1.0 if n == 0 else math.pow(self.VolumeMultiplier, n + 1)
vol = self._norm_vol(base_vol * coeff)
if vol <= 0 or vol > self.MaxVolume:
return
sd = self.StopLossPoints * step
td = self.TakeProfitPoints * step
self.Volume = vol
self.SellMarket()
self._short_positions.append({
"entry": float(candle.ClosePrice), "volume": vol,
"stop": float(candle.ClosePrice) + sd if sd > 0 else 0,
"take": float(candle.ClosePrice) - td if td > 0 else 0
})
self._last_entry_price = float(candle.ClosePrice)
self._cooldown = 3
def _calc_risk_vol(self, price_step):
if self.StopLossPoints <= 0:
return 0
sd = self.StopLossPoints * price_step
if sd <= 0 or self.Portfolio is None:
return 0
eq = float(self.Portfolio.CurrentValue) if self.Portfolio.CurrentValue is not None else 0
if eq <= 0:
return 0
return eq * (self.RiskPercent / 100.0) / sd
def _calc_total_profit(self, price):
p = 0.0
for s in self._long_positions:
p += (price - s["entry"]) * s["volume"]
for s in self._short_positions:
p += (s["entry"] - price) * s["volume"]
return p
def _close_most_profitable(self, price):
best = None
best_long = False
best_pnl = 0.0
for s in self._long_positions:
pnl = (price - s["entry"]) * s["volume"]
if pnl > best_pnl:
best_pnl = pnl; best = s; best_long = True
for s in self._short_positions:
pnl = (s["entry"] - price) * s["volume"]
if pnl > best_pnl:
best_pnl = pnl; best = s; best_long = False
if best is None or best_pnl <= 0:
return
if best_long:
self.SellMarket()
self._long_positions.remove(best)
else:
self.BuyMarket()
self._short_positions.remove(best)
self._last_entry_price = 0.0
def _norm_vol(self, vol):
if vol <= 0:
return 0
sec = self.Security
step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0
if step > 0:
vol = math.floor(vol / step) * step
return vol
def _get_price_step(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0
if step > 0:
return step
d = sec.Decimals if sec is not None and sec.Decimals is not None else 0
if d > 0:
return math.pow(10, -d)
return 0.0001
def OnReseted(self):
super(dealers_trade_macd_strategy, self).OnReseted()
self._previous_macd = None
self._last_entry_price = 0.0
self._cooldown = 0
self._long_positions = []
self._short_positions = []
def CreateClone(self):
return dealers_trade_macd_strategy()