Стратегия Ma Shift Puria Method
Общее описание
Ma Shift Puria Method — адаптация классического советника Puria под высокоуровневый API StockSharp. Стратегия объединяет две экспоненциальные скользящие средние, фильтр MACD и при необходимости динамическое сопровождение позиции по фракталам. Все решения принимаются только на закрытых свечах. Управление позицией включает фиксированные стоп-лоссы/тейк-профиты, параметризованный трейлинг и дополнительную фиксацию прибыли на основе последнего фрактального экстремума.
Используемые индикаторы
- Быстрая EMA (по умолчанию 14) — отслеживает краткосрочное ускорение цены и формирует основной сигнал.
- Медленная EMA (по умолчанию 80) — задаёт направление тренда; сигнал считается валидным только при достаточном отрыве между средними.
- MACD (fast 11, slow 102, signal 9) — подтверждает импульс: линия MACD должна пересечь нулевую ось в сторону сделки, а три свечи назад находиться по противоположную сторону.
- Фракталы (окно 5 свечей) — используются, если активирован фрактальный трейлинг; экстремум определяется по пяти последовательным барам аналогично MetaTrader.
Логика входа
Стратегия открывает позицию только если торговля разрешена и последняя закрывшаяся свеча удовлетворяет набору условий.
Покупка
- Быстрая EMA выше медленной.
- Медленная EMA растёт относительно значения три свечи назад.
- Быстрая EMA увеличивается (текущее значение больше предыдущего).
- MACD находится выше нуля, а три свечи назад был ниже нуля.
- Прирост быстрой EMA между последними барами превышает порог Shift Minimum в пунктах и либо усиливается, либо предыдущий прирост был неположительным.
Продажа
- Быстрая EMA ниже медленной.
- Медленная EMA снижается относительно значения три свечи назад.
- Быстрая EMA убывает (текущее значение меньше предыдущего).
- MACD ниже нулевой линии, а три свечи назад был выше неё.
- Снижение быстрой EMA превышает порог Shift Minimum, при этом ускорение сохраняется либо предыдущий сдвиг был неотрицательным.
Объём позиции формируется либо фиксированным значением (ManualVolume), либо динамически по доле риска (RiskPercent). При смене направления стратегия закрывает встречную позицию и сразу открывает новую в требуемую сторону.
Управление позицией и рисками
- Стоп-лосс — указывается в пунктах от цены входа и проверяется на каждой свече; при достижении уровень закрывает позицию.
- Тейк-профит — аналогично стопу, фиксирует прибыль целиком.
- Трейлинг-стоп — активируется после прохождения ценой расстояния
TrailingStopPips + TrailingStepPips; уровень стопа переносится на расстояниеTrailingStopPips, если может быть улучшен минимум наTrailingStepPips, что повторяет оригинальный MQL-алгоритм. - Фрактальный трейлинг — опция. Когда цена проходит 95% расстояния до тейк-профита, стоп может быть подтянут к последнему фрактальному минимуму (для лонга) или максимуму (для шорта), что позволяет быстрее зафиксировать прибыль около цели.
- Риск-менеджмент — в режиме динамического объёма рассчитывается денежный риск (доля от капитала), который делится на денежное выражение стоп-лосса и приводится к допустимому шагу лота с учётом ограничений площадки.
Параметры
| Имя | Описание | Значение по умолчанию |
|---|---|---|
UseManualVolume |
Использовать фиксированный объём. | true |
ManualVolume |
Размер позиции при фиксированном объёме. | 0.1 |
RiskPercent |
Доля капитала, рискуемая в сделке (для динамического объёма). | 9 |
StopLossPips |
Стоп-лосс в пунктах. | 45 |
TakeProfitPips |
Тейк-профит в пунктах. | 75 |
TrailingStopPips |
Расстояние трейлинг-стопа. | 15 |
TrailingStepPips |
Минимальный шаг обновления трейлинг-стопа. | 5 |
MaxPositions |
Максимальное число добавленных единиц в одном направлении. | 1 |
ShiftMinPips |
Минимальный наклон быстрой EMA для сигнала. | 20 |
FastLength |
Период быстрой EMA. | 14 |
SlowLength |
Период медленной EMA. | 80 |
MacdFast |
Быстрый период MACD. | 11 |
MacdSlow |
Медленный период MACD. | 102 |
UseFractalTrailing |
Включить фрактальный трейлинг. | false |
CandleType |
Тип/таймфрейм свечей для расчётов. | 15 минут |
Особенности реализации
- Подписка на свечи и индикаторы выполняется через
SubscribeCandles().Bind(...), что исключает прямой доступ к буферам индикаторов. - Для имитации сдвигов MQL хранится история трёх последних значений EMA и MACD, не используются методы
GetValue. - Фракталы рассчитываются во внутренних массивах из пяти баров, поэтому соответствуют классическому определению и не требуют дополнительных индикаторов.
- Стопы и тейки реализованы через рыночные ордера при достижении уровней, то есть стратегия ведёт себя как исходный советник, который модифицировал существующую позицию.
- В
OnStartedвызываетсяStartProtection(), чтобы автоматически контролировать открытые позиции при возможных сбоях соединения.
Рекомендации по использованию
- Подбирайте таймфрейм, соответствующий оригинальной настройке Puria (часто 15-минутные свечи на валютных парах).
- Корректируйте параметры в пунктах под специфику инструмента (число знаков, стоимость пункта).
- Перед включением динамического объёма убедитесь, что данные по капиталу и шагу лота корректны, иначе расчётный объём может быть недоступен для торговли.
- При необходимости комбинируйте стратегию с фильтрацией торговых сессий или общим портфельным управлением — реализованная логика фокусируется на сигнале и сопровождении позиции по методике Puria.
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>
/// Puria method strategy that monitors EMA slopes and MACD momentum with optional trailing management.
/// </summary>
public class MaShiftPuriaMethodStrategy : Strategy
{
private readonly StrategyParam<bool> _useManualVolume;
private readonly StrategyParam<decimal> _manualVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _shiftMinPips;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<bool> _useFractalTrailing;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma = null!;
private ExponentialMovingAverage _slowEma = null!;
private MovingAverageConvergenceDivergence _macd = null!;
private decimal? _fastPrev1;
private decimal? _fastPrev2;
private decimal? _fastPrev3;
private decimal? _slowPrev1;
private decimal? _slowPrev2;
private decimal? _slowPrev3;
private decimal? _macdPrev1;
private decimal? _macdPrev2;
private decimal? _macdPrev3;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longStopPrice;
private decimal? _shortStopPrice;
private decimal? _longTakePrice;
private decimal? _shortTakePrice;
private readonly decimal[] _highWindow = new decimal[5];
private readonly decimal[] _lowWindow = new decimal[5];
private int _fractalCount;
private decimal? _lastUpperFractal;
private decimal? _lastLowerFractal;
/// <summary>
/// Use manual volume instead of risk-based sizing.
/// </summary>
public bool UseManualVolume
{
get => _useManualVolume.Value;
set => _useManualVolume.Value = value;
}
/// <summary>
/// Manual trade volume.
/// </summary>
public decimal ManualVolume
{
get => _manualVolume.Value;
set => _manualVolume.Value = value;
}
/// <summary>
/// Risk percentage used when calculating position size.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.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>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum move required before updating the trailing stop.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Maximum number of position units allowed in one direction.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Minimum EMA separation (in pips) required for a valid signal.
/// </summary>
public decimal ShiftMinPips
{
get => _shiftMinPips.Value;
set => _shiftMinPips.Value = value;
}
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// MACD fast period.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// MACD slow period.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Enable fractal-based trailing stop adjustments.
/// </summary>
public bool UseFractalTrailing
{
get => _useFractalTrailing.Value;
set => _useFractalTrailing.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public MaShiftPuriaMethodStrategy()
{
_useManualVolume = Param(nameof(UseManualVolume), true)
.SetDisplay("Manual Volume", "Use fixed trade volume", "Risk");
_manualVolume = Param(nameof(ManualVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Fixed trade volume", "Risk");
_riskPercent = Param(nameof(RiskPercent), 9m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Portfolio risk percent per trade", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 45)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 75)
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 15)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimum advance before trailing", "Risk");
_maxPositions = Param(nameof(MaxPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum units per direction", "Risk");
_shiftMinPips = Param(nameof(ShiftMinPips), 20m)
.SetGreaterThanZero()
.SetDisplay("Shift Minimum", "Minimal EMA separation in pips", "Signals");
_fastLength = Param(nameof(FastLength), 14)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
_slowLength = Param(nameof(SlowLength), 80)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
_macdFast = Param(nameof(MacdFast), 11)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast period", "Indicators");
_macdSlow = Param(nameof(MacdSlow), 102)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow period", "Indicators");
_useFractalTrailing = Param(nameof(UseFractalTrailing), false)
.SetDisplay("Fractal Trailing", "Enable fractal trailing stop", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candles used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastPrev1 = null;
_fastPrev2 = null;
_fastPrev3 = null;
_slowPrev1 = null;
_slowPrev2 = null;
_slowPrev3 = null;
_macdPrev1 = null;
_macdPrev2 = null;
_macdPrev3 = null;
ResetLongState();
ResetShortState();
Array.Clear(_highWindow, 0, _highWindow.Length);
Array.Clear(_lowWindow, 0, _lowWindow.Length);
_fractalCount = 0;
_lastUpperFractal = null;
_lastLowerFractal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new ExponentialMovingAverage { Length = FastLength };
_slowEma = new ExponentialMovingAverage { Length = SlowLength };
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
};
Volume = ManualVolume;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastEma, _slowEma, _macd, ProcessCandle)
.Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, subscription);
DrawIndicator(priceArea, _fastEma);
DrawIndicator(priceArea, _slowEma);
var macdArea = CreateChartArea();
if (macdArea != null)
{
macdArea.Title = "MACD";
DrawIndicator(macdArea, _macd);
}
DrawOwnTrades(priceArea);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal macdMain)
{
if (candle.State != CandleStates.Finished)
return;
var pip = GetPipSize();
UpdateFractals(candle);
var prevFast1 = _fastPrev1;
var prevFast2 = _fastPrev2;
var prevFast3 = _fastPrev3;
var prevSlow1 = _slowPrev1;
var prevSlow3 = _slowPrev3;
var prevMacd1 = _macdPrev1;
var prevMacd3 = _macdPrev3;
UpdateHistory(fast, slow, macdMain);
ManageLongPosition(candle, pip);
ManageShortPosition(candle, pip);
if (!prevFast1.HasValue || !prevFast2.HasValue || !prevFast3.HasValue ||
!prevSlow1.HasValue || !prevSlow3.HasValue || !prevMacd1.HasValue || !prevMacd3.HasValue)
{
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var fast1 = prevFast1.Value;
var fast2 = prevFast2.Value;
var fast3 = prevFast3.Value;
var slow1 = prevSlow1.Value;
var slow3 = prevSlow3.Value;
var macd1 = prevMacd1.Value;
var macd3 = prevMacd3.Value;
if (pip <= 0m)
pip = 0.0001m;
var x1Long = (fast1 - fast2) / pip;
var x2Long = (fast2 - fast3) / pip;
var x1Short = (fast2 - fast1) / pip;
var x2Short = (fast3 - fast2) / pip;
var shiftRequirement = ShiftMinPips;
var buySignal = fast1 > slow1 &&
slow1 > slow3 &&
fast1 > fast2 &&
macd1 > 0m &&
macd3 < 0m &&
x1Long > shiftRequirement &&
(x1Long >= x2Long || x2Long <= 0m);
var sellSignal = fast1 < slow1 &&
slow1 < slow3 &&
fast1 < fast2 &&
macd1 < 0m &&
macd3 > 0m &&
x1Short > shiftRequirement &&
(x1Short >= x2Short || x2Short <= 0m);
if (buySignal)
{
TryEnterLong(candle, pip);
}
else if (sellSignal)
{
TryEnterShort(candle, pip);
}
}
private void TryEnterLong(ICandleMessage candle, decimal pip)
{
var stopDistance = StopLossPips > 0 ? StopLossPips * pip : 0m;
var volumePerTrade = GetTradeVolume(stopDistance);
if (volumePerTrade <= 0m)
return;
var maxVolume = volumePerTrade * MaxPositions;
if (maxVolume <= 0m)
return;
var limit = maxVolume - Position;
if (limit <= 0m)
return;
var volumeToBuy = volumePerTrade;
if (Position < 0m)
volumeToBuy += -Position;
if (volumeToBuy > limit)
volumeToBuy = limit;
if (volumeToBuy <= 0m)
return;
BuyMarket(volumeToBuy);
_longEntryPrice = candle.ClosePrice;
_longStopPrice = StopLossPips > 0 ? candle.ClosePrice - stopDistance : null;
_longTakePrice = TakeProfitPips > 0 ? candle.ClosePrice + TakeProfitPips * pip : null;
ResetShortState();
}
private void TryEnterShort(ICandleMessage candle, decimal pip)
{
var stopDistance = StopLossPips > 0 ? StopLossPips * pip : 0m;
var volumePerTrade = GetTradeVolume(stopDistance);
if (volumePerTrade <= 0m)
return;
var maxVolume = volumePerTrade * MaxPositions;
if (maxVolume <= 0m)
return;
var limit = maxVolume + Position;
if (limit <= 0m)
return;
var volumeToSell = volumePerTrade;
if (Position > 0m)
volumeToSell += Position;
if (volumeToSell > limit)
volumeToSell = limit;
if (volumeToSell <= 0m)
return;
SellMarket(volumeToSell);
_shortEntryPrice = candle.ClosePrice;
_shortStopPrice = StopLossPips > 0 ? candle.ClosePrice + stopDistance : null;
_shortTakePrice = TakeProfitPips > 0 ? candle.ClosePrice - TakeProfitPips * pip : null;
ResetLongState();
}
private void ManageLongPosition(ICandleMessage candle, decimal pip)
{
if (Position <= 0m)
{
ResetLongState();
return;
}
if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetLongState();
return;
}
if (_longTakePrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetLongState();
return;
}
if (TrailingStopPips > 0 && _longEntryPrice is decimal entry)
{
var distance = TrailingStopPips * pip;
var step = TrailingStepPips * pip;
if (distance > 0m)
{
var profit = candle.ClosePrice - entry;
if (profit > (TrailingStopPips + TrailingStepPips) * pip)
{
var threshold = candle.ClosePrice - (distance + step);
if (!_longStopPrice.HasValue || _longStopPrice.Value < threshold)
{
_longStopPrice = candle.ClosePrice - distance;
}
}
}
}
if (UseFractalTrailing && _longEntryPrice is decimal longEntry && _longStopPrice.HasValue && TakeProfitPips > 0)
{
var target = TakeProfitPips * pip;
if (target > 0m)
{
var profit = candle.ClosePrice - longEntry;
if (profit >= 0.95m * target && _lastLowerFractal is decimal lower && lower > _longStopPrice.Value)
{
_longStopPrice = lower;
}
}
}
if (_longStopPrice is decimal trailing && candle.LowPrice <= trailing)
{
SellMarket(Position);
ResetLongState();
}
}
private void ManageShortPosition(ICandleMessage candle, decimal pip)
{
if (Position >= 0m)
{
ResetShortState();
return;
}
var shortVolume = Math.Abs(Position);
if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(shortVolume);
ResetShortState();
return;
}
if (_shortTakePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(shortVolume);
ResetShortState();
return;
}
if (TrailingStopPips > 0 && _shortEntryPrice is decimal entry)
{
var distance = TrailingStopPips * pip;
var step = TrailingStepPips * pip;
if (distance > 0m)
{
var profit = entry - candle.ClosePrice;
if (profit > (TrailingStopPips + TrailingStepPips) * pip)
{
var threshold = candle.ClosePrice + (distance + step);
if (!_shortStopPrice.HasValue || _shortStopPrice.Value > threshold)
{
_shortStopPrice = candle.ClosePrice + distance;
}
}
}
}
if (UseFractalTrailing && _shortEntryPrice is decimal shortEntry && _shortStopPrice.HasValue && TakeProfitPips > 0)
{
var target = TakeProfitPips * pip;
if (target > 0m)
{
var profit = shortEntry - candle.ClosePrice;
if (profit >= 0.95m * target && _lastUpperFractal is decimal upper && upper < _shortStopPrice.Value)
{
_shortStopPrice = upper;
}
}
}
if (_shortStopPrice is decimal trailing && candle.HighPrice >= trailing)
{
BuyMarket(shortVolume);
ResetShortState();
}
}
private void UpdateHistory(decimal fast, decimal slow, decimal macdMain)
{
_fastPrev3 = _fastPrev2;
_fastPrev2 = _fastPrev1;
_fastPrev1 = fast;
_slowPrev3 = _slowPrev2;
_slowPrev2 = _slowPrev1;
_slowPrev1 = slow;
_macdPrev3 = _macdPrev2;
_macdPrev2 = _macdPrev1;
_macdPrev1 = macdMain;
}
private void UpdateFractals(ICandleMessage candle)
{
for (var i = 0; i < _highWindow.Length - 1; i++)
{
_highWindow[i] = _highWindow[i + 1];
_lowWindow[i] = _lowWindow[i + 1];
}
_highWindow[^1] = candle.HighPrice;
_lowWindow[^1] = candle.LowPrice;
if (_fractalCount < _highWindow.Length)
_fractalCount++;
if (_fractalCount < _highWindow.Length)
return;
var center = _highWindow.Length / 2;
var potentialUpper = _highWindow[center];
var potentialLower = _lowWindow[center];
var isUpper = true;
for (var i = 0; i < _highWindow.Length; i++)
{
if (i == center)
continue;
if (_highWindow[i] >= potentialUpper)
{
isUpper = false;
break;
}
}
if (isUpper)
_lastUpperFractal = potentialUpper;
var isLower = true;
for (var i = 0; i < _lowWindow.Length; i++)
{
if (i == center)
continue;
if (_lowWindow[i] <= potentialLower)
{
isLower = false;
break;
}
}
if (isLower)
_lastLowerFractal = potentialLower;
}
private decimal GetTradeVolume(decimal stopDistance)
{
if (UseManualVolume || stopDistance <= 0m)
return ManualVolume;
var portfolioValue = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (portfolioValue <= 0m)
return ManualVolume;
var riskAmount = portfolioValue * RiskPercent / 100m;
if (riskAmount <= 0m)
return ManualVolume;
var volume = riskAmount / stopDistance;
if (volume <= 0m)
return ManualVolume;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
var stepsCount = Math.Floor((double)(volume / step));
volume = stepsCount <= 0 ? step : (decimal)stepsCount * step;
}
var minVolume = Security?.MinVolume ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
var maxVolume = Security?.MaxVolume ?? 0m;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
return volume;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep;
if (step is null || step <= 0m)
return 0.0001m;
if (step < 0.01m)
return step.Value * 10m;
return step.Value;
}
private void ResetLongState()
{
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
}
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 (
ExponentialMovingAverage, MovingAverageConvergenceDivergence
)
from StockSharp.Algo.Strategies import Strategy
class ma_shift_puria_method_strategy(Strategy):
def __init__(self):
super(ma_shift_puria_method_strategy, self).__init__()
self._use_manual_volume = self.Param("UseManualVolume", True)
self._manual_volume = self.Param("ManualVolume", 0.1)
self._risk_percent = self.Param("RiskPercent", 9.0)
self._stop_loss_pips = self.Param("StopLossPips", 45)
self._take_profit_pips = self.Param("TakeProfitPips", 75)
self._trailing_stop_pips = self.Param("TrailingStopPips", 15)
self._trailing_step_pips = self.Param("TrailingStepPips", 5)
self._max_positions = self.Param("MaxPositions", 1)
self._shift_min_pips = self.Param("ShiftMinPips", 20.0)
self._fast_length = self.Param("FastLength", 14)
self._slow_length = self.Param("SlowLength", 80)
self._macd_fast = self.Param("MacdFast", 11)
self._macd_slow = self.Param("MacdSlow", 102)
self._use_fractal_trailing = self.Param("UseFractalTrailing", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._fast_ema = None
self._slow_ema = None
self._macd = None
self._fast_prev1 = None
self._fast_prev2 = None
self._fast_prev3 = None
self._slow_prev1 = None
self._slow_prev2 = None
self._slow_prev3 = None
self._macd_prev1 = None
self._macd_prev2 = None
self._macd_prev3 = None
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
self._high_window = [0.0] * 5
self._low_window = [0.0] * 5
self._fractal_count = 0
self._last_upper_fractal = None
self._last_lower_fractal = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def UseManualVolume(self):
return self._use_manual_volume.Value
@property
def ManualVolume(self):
return self._manual_volume.Value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def MaxPositions(self):
return self._max_positions.Value
@property
def ShiftMinPips(self):
return self._shift_min_pips.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def MacdFast(self):
return self._macd_fast.Value
@property
def MacdSlow(self):
return self._macd_slow.Value
@property
def UseFractalTrailing(self):
return self._use_fractal_trailing.Value
def OnStarted2(self, time):
super(ma_shift_puria_method_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.MacdSlow
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.MacdFast
self._macd = MovingAverageConvergenceDivergence(slow_ma, fast_ma)
self.Volume = self.ManualVolume
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_ema)
self.DrawIndicator(area, self._slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast, slow, macd_main):
if candle.State != CandleStates.Finished:
return
pip = self._get_pip_size()
self._update_fractals(candle)
prev_fast1 = self._fast_prev1
prev_fast2 = self._fast_prev2
prev_fast3 = self._fast_prev3
prev_slow1 = self._slow_prev1
prev_slow3 = self._slow_prev3
prev_macd1 = self._macd_prev1
prev_macd3 = self._macd_prev3
self._update_history(float(fast), float(slow), float(macd_main))
self._manage_long_position(candle, pip)
self._manage_short_position(candle, pip)
if (prev_fast1 is None or prev_fast2 is None or prev_fast3 is None or
prev_slow1 is None or prev_slow3 is None or
prev_macd1 is None or prev_macd3 is None):
return
if pip <= 0:
pip = 0.0001
f1 = prev_fast1; f2 = prev_fast2; f3 = prev_fast3
s1 = prev_slow1; s3 = prev_slow3
m1 = prev_macd1; m3 = prev_macd3
x1_long = (f1 - f2) / pip
x2_long = (f2 - f3) / pip
x1_short = (f2 - f1) / pip
x2_short = (f3 - f2) / pip
shift_req = self.ShiftMinPips
buy_signal = (f1 > s1 and s1 > s3 and f1 > f2 and
m1 > 0 and m3 < 0 and
x1_long > shift_req and (x1_long >= x2_long or x2_long <= 0))
sell_signal = (f1 < s1 and s1 < s3 and f1 < f2 and
m1 < 0 and m3 > 0 and
x1_short > shift_req and (x1_short >= x2_short or x2_short <= 0))
if buy_signal:
self._try_enter_long(candle, pip)
elif sell_signal:
self._try_enter_short(candle, pip)
def _try_enter_long(self, candle, pip):
self.BuyMarket()
price = float(candle.ClosePrice)
sd = self.StopLossPips * pip if self.StopLossPips > 0 else 0
self._long_entry_price = price
self._long_stop_price = price - sd if sd > 0 else None
self._long_take_price = price + self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
self._reset_short_state()
def _try_enter_short(self, candle, pip):
self.SellMarket()
price = float(candle.ClosePrice)
sd = self.StopLossPips * pip if self.StopLossPips > 0 else 0
self._short_entry_price = price
self._short_stop_price = price + sd if sd > 0 else None
self._short_take_price = price - self.TakeProfitPips * pip if self.TakeProfitPips > 0 else None
self._reset_long_state()
def _manage_long_position(self, candle, pip):
if self.Position <= 0:
self._reset_long_state()
return
if self._long_stop_price is not None and float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket(); self._reset_long_state(); return
if self._long_take_price is not None and float(candle.HighPrice) >= self._long_take_price:
self.SellMarket(); self._reset_long_state(); return
if self.TrailingStopPips > 0 and self._long_entry_price is not None:
dist = self.TrailingStopPips * pip
step = self.TrailingStepPips * pip
if dist > 0:
profit = float(candle.ClosePrice) - self._long_entry_price
if profit > (self.TrailingStopPips + self.TrailingStepPips) * pip:
threshold = float(candle.ClosePrice) - (dist + step)
if self._long_stop_price is None or self._long_stop_price < threshold:
self._long_stop_price = float(candle.ClosePrice) - dist
if (self.UseFractalTrailing and self._long_entry_price is not None and
self._long_stop_price is not None and self.TakeProfitPips > 0):
target = self.TakeProfitPips * pip
if target > 0:
profit = float(candle.ClosePrice) - self._long_entry_price
if profit >= 0.95 * target and self._last_lower_fractal is not None:
if self._last_lower_fractal > self._long_stop_price:
self._long_stop_price = self._last_lower_fractal
if self._long_stop_price is not None and float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket(); self._reset_long_state()
def _manage_short_position(self, candle, pip):
if self.Position >= 0:
self._reset_short_state()
return
if self._short_stop_price is not None and float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket(); self._reset_short_state(); return
if self._short_take_price is not None and float(candle.LowPrice) <= self._short_take_price:
self.BuyMarket(); self._reset_short_state(); return
if self.TrailingStopPips > 0 and self._short_entry_price is not None:
dist = self.TrailingStopPips * pip
step = self.TrailingStepPips * pip
if dist > 0:
profit = self._short_entry_price - float(candle.ClosePrice)
if profit > (self.TrailingStopPips + self.TrailingStepPips) * pip:
threshold = float(candle.ClosePrice) + (dist + step)
if self._short_stop_price is None or self._short_stop_price > threshold:
self._short_stop_price = float(candle.ClosePrice) + dist
if (self.UseFractalTrailing and self._short_entry_price is not None and
self._short_stop_price is not None and self.TakeProfitPips > 0):
target = self.TakeProfitPips * pip
if target > 0:
profit = self._short_entry_price - float(candle.ClosePrice)
if profit >= 0.95 * target and self._last_upper_fractal is not None:
if self._last_upper_fractal < self._short_stop_price:
self._short_stop_price = self._last_upper_fractal
if self._short_stop_price is not None and float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket(); self._reset_short_state()
def _update_history(self, fast, slow, macd_main):
self._fast_prev3 = self._fast_prev2
self._fast_prev2 = self._fast_prev1
self._fast_prev1 = fast
self._slow_prev3 = self._slow_prev2
self._slow_prev2 = self._slow_prev1
self._slow_prev1 = slow
self._macd_prev3 = self._macd_prev2
self._macd_prev2 = self._macd_prev1
self._macd_prev1 = macd_main
def _update_fractals(self, candle):
for i in range(len(self._high_window) - 1):
self._high_window[i] = self._high_window[i + 1]
self._low_window[i] = self._low_window[i + 1]
self._high_window[-1] = float(candle.HighPrice)
self._low_window[-1] = float(candle.LowPrice)
if self._fractal_count < len(self._high_window):
self._fractal_count += 1
if self._fractal_count < len(self._high_window):
return
center = len(self._high_window) // 2
p_upper = self._high_window[center]
p_lower = self._low_window[center]
is_upper = True
for i in range(len(self._high_window)):
if i == center:
continue
if self._high_window[i] >= p_upper:
is_upper = False
break
if is_upper:
self._last_upper_fractal = p_upper
is_lower = True
for i in range(len(self._low_window)):
if i == center:
continue
if self._low_window[i] <= p_lower:
is_lower = False
break
if is_lower:
self._last_lower_fractal = p_lower
def _get_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if step <= 0:
return 0.0001
if step < 0.01:
return step * 10.0
return step
def _reset_long_state(self):
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
def _reset_short_state(self):
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
def OnReseted(self):
super(ma_shift_puria_method_strategy, self).OnReseted()
self._fast_prev1 = None
self._fast_prev2 = None
self._fast_prev3 = None
self._slow_prev1 = None
self._slow_prev2 = None
self._slow_prev3 = None
self._macd_prev1 = None
self._macd_prev2 = None
self._macd_prev3 = None
self._reset_long_state()
self._reset_short_state()
self._high_window = [0.0] * 5
self._low_window = [0.0] * 5
self._fractal_count = 0
self._last_upper_fractal = None
self._last_lower_fractal = None
def CreateClone(self):
return ma_shift_puria_method_strategy()