Cash Machine 5 min Legacy
Обзор
Cash Machine 5 min Legacy — порт советника MetaTrader 4 CashMachine_5min на платформу StockSharp. Стратегия отслеживает разворотные импульсы по индикаторам DeMarker и Stochastic на пятиминутных свечах. После входа защитные уровни стоп-лосса и тейк-профита остаются скрытыми во внутренней логике, чтобы не публиковать их на стороне брокера. При росте прибыли включается поэтапная защита на трёх настраиваемых уровнях.
Логика стратегии
Условия входа
- Длинная позиция — DeMarker должен пересечь снизу вверх уровень 0.30, а линия %K стохастика на той же свече пересекает уровень 20 вверх. Оба условия отслеживаются по переходу между предыдущей завершённой свечой и текущей. При отсутствии позиции совершается рыночная покупка с указанным объёмом.
- Короткая позиция — зеркально: DeMarker пересекает уровень 0.70 сверху вниз, а %K опускается ниже 80. Сигнал активен только если на прошлой свече индикаторы находились по другую сторону границ. Открывается короткая позиция рыночной продажей при отсутствии позиций.
Сопровождение сделки
- Скрытые ограничения — лонг закрывается, когда цена падает на величину
Hidden Stop Lossили растёт наHidden Take Profit. Для шорта условия зеркальны. Эти уровни контролируются кодом без регистрации реальных стоп-заявок. - Ступенчатый трейлинг — три целевых уровня (
Target TP1,Target TP2,Target TP3) подтягивают скрытый стоп по мере продвижения цены. Для лонга стоп переносится на максимум свечи минус(target − 13)пунктов; для шорта — на минимум плюс(target + 13)пунктов. Каждая ступень срабатывает только один раз и не ослабляется. - Закрытие по трейлингу — после активации хотя бы одной ступени касание стопа приводит к закрытию позиции рыночным ордером.
Дополнительные механизмы
- Размер пункта определяется автоматически по шагу цены инструмента, корректно обрабатывая четырёх- и пятизначные валютные котировки (а также трёх- и двухзначные кроссы).
- Индикаторы и сигналы рассчитываются на выбранном типе свечей (по умолчанию пятиминутные). Обрабатываются только завершённые свечи.
Параметры
- Hidden Take Profit — скрытая дистанция тейк-профита в пунктах (по умолчанию
60). - Hidden Stop Loss — скрытая дистанция стоп-лосса в пунктах (по умолчанию
30). - Target TP1 / TP2 / TP3 — уровни фиксации прибыли, активирующие ступени трейлинга (по умолчанию
20,35,50). - Order Volume — объём рыночных заявок при входе (по умолчанию
0.2). - DeMarker Length — период усреднения индикатора DeMarker (по умолчанию
14). - Stochastic Length — базовый период стохастика (по умолчанию
5). - Stochastic %K — сглаживание линии %K (по умолчанию
3). - Stochastic %D — сглаживание линии %D (по умолчанию
3). - Candle Type — таймфрейм расчётов (по умолчанию пятиминутные свечи).
Дополнительная информация
- Стратегия работает только с одной позицией и не переворачивается немедленно — новый вход возможен после закрытия текущей сделки.
- Защитные уровни реализованы через рыночные заявки, поэтому стопы отсутствуют в стакане заявок.
- В пакете представлена только реализация на 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>
/// Cash Machine strategy converted from the MetaTrader 4 expert advisor.
/// Uses DeMarker and Stochastic oscillator crossovers on five minute candles
/// and gradually tightens a hidden stop when profit targets are reached.
/// </summary>
public class CashMachine5minLegacyStrategy : Strategy
{
private readonly StrategyParam<decimal> _hiddenTakeProfit;
private readonly StrategyParam<decimal> _hiddenStopLoss;
private readonly StrategyParam<decimal> _targetTp1;
private readonly StrategyParam<decimal> _targetTp2;
private readonly StrategyParam<decimal> _targetTp3;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _deMarkerLength;
private readonly StrategyParam<int> _stochasticLength;
private readonly StrategyParam<int> _stochasticK;
private readonly StrategyParam<int> _stochasticD;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousDeMarker;
private decimal? _previousStochasticK;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private int _longStage;
private int _shortStage;
private decimal _pipSize;
private decimal _entryPrice;
/// <summary>
/// Hidden take profit distance expressed in pips.
/// </summary>
public decimal HiddenTakeProfit
{
get => _hiddenTakeProfit.Value;
set => _hiddenTakeProfit.Value = value;
}
/// <summary>
/// Hidden stop loss distance expressed in pips.
/// </summary>
public decimal HiddenStopLoss
{
get => _hiddenStopLoss.Value;
set => _hiddenStopLoss.Value = value;
}
/// <summary>
/// First profit threshold in pips.
/// </summary>
public decimal TargetTp1
{
get => _targetTp1.Value;
set => _targetTp1.Value = value;
}
/// <summary>
/// Second profit threshold in pips.
/// </summary>
public decimal TargetTp2
{
get => _targetTp2.Value;
set => _targetTp2.Value = value;
}
/// <summary>
/// Third profit threshold in pips.
/// </summary>
public decimal TargetTp3
{
get => _targetTp3.Value;
set => _targetTp3.Value = value;
}
/// <summary>
/// Order volume used when opening new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// DeMarker averaging period.
/// </summary>
public int DeMarkerLength
{
get => _deMarkerLength.Value;
set => _deMarkerLength.Value = value;
}
/// <summary>
/// Stochastic oscillator length.
/// </summary>
public int StochasticLength
{
get => _stochasticLength.Value;
set => _stochasticLength.Value = value;
}
/// <summary>
/// %K smoothing factor for the Stochastic oscillator.
/// </summary>
public int StochasticK
{
get => _stochasticK.Value;
set => _stochasticK.Value = value;
}
/// <summary>
/// %D smoothing factor for the Stochastic oscillator.
/// </summary>
public int StochasticD
{
get => _stochasticD.Value;
set => _stochasticD.Value = value;
}
/// <summary>
/// Candle type that drives indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CashMachine5minLegacyStrategy"/> class.
/// </summary>
public CashMachine5minLegacyStrategy()
{
_hiddenTakeProfit = Param(nameof(HiddenTakeProfit), 60m)
.SetGreaterThanZero()
.SetDisplay("Hidden Take Profit", "Hidden take profit distance in pips", "Risk");
_hiddenStopLoss = Param(nameof(HiddenStopLoss), 30m)
.SetGreaterThanZero()
.SetDisplay("Hidden Stop Loss", "Hidden stop loss distance in pips", "Risk");
_targetTp1 = Param(nameof(TargetTp1), 20m)
.SetGreaterThanZero()
.SetDisplay("Target TP1", "First profit threshold", "Risk");
_targetTp2 = Param(nameof(TargetTp2), 35m)
.SetGreaterThanZero()
.SetDisplay("Target TP2", "Second profit threshold", "Risk");
_targetTp3 = Param(nameof(TargetTp3), 50m)
.SetGreaterThanZero()
.SetDisplay("Target TP3", "Third profit threshold", "Risk");
_orderVolume = Param(nameof(OrderVolume), 0.2m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Order volume for new trades", "Trading");
_deMarkerLength = Param(nameof(DeMarkerLength), 14)
.SetGreaterThanZero()
.SetDisplay("DeMarker Length", "DeMarker averaging period", "Indicators");
_stochasticLength = Param(nameof(StochasticLength), 5)
.SetGreaterThanZero()
.SetDisplay("Stochastic Length", "Base Stochastic length", "Indicators");
_stochasticK = Param(nameof(StochasticK), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "%K smoothing length", "Indicators");
_stochasticD = Param(nameof(StochasticD), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "%D smoothing length", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_pipSize = 0.0001m;
_entryPrice = 0m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousDeMarker = null;
_previousStochasticK = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_longStage = 0;
_shortStage = 0;
_pipSize = 0.0001m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
var deMarker = new DeMarker
{
Length = DeMarkerLength,
};
var stochastic = new StochasticOscillator();
stochastic.K.Length = StochasticLength;
stochastic.D.Length = StochasticD;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(deMarker, stochastic, ProcessCandle)
.Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, subscription);
DrawIndicator(priceArea, deMarker);
var oscillatorArea = CreateChartArea();
if (oscillatorArea != null)
{
DrawIndicator(oscillatorArea, stochastic);
}
DrawOwnTrades(priceArea);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue deMarkerValue, IIndicatorValue stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochasticValue.IsFinal || !IsFormedAndOnlineAndAllowTrading())
return;
var deMarker = deMarkerValue.ToDecimal();
var stochastic = (StochasticOscillatorValue)stochasticValue;
if (stochastic.K is not decimal currentK)
return;
if (Position == 0)
{
// Reset trailing state whenever the strategy is flat.
_longStage = 0;
_shortStage = 0;
_longTrailingStop = null;
_shortTrailingStop = null;
}
if (Position == 0 && _previousDeMarker is decimal prevDe && _previousStochasticK is decimal prevK)
{
var longSignal = prevDe < 0.30m && deMarker >= 0.30m && prevK < 20m && currentK >= 20m;
var shortSignal = prevDe > 0.70m && deMarker <= 0.70m && prevK > 80m && currentK <= 80m;
if (longSignal && OrderVolume > 0m)
{
// Both oscillators crossed up from oversold zones.
_entryPrice = candle.ClosePrice;
BuyMarket(OrderVolume);
}
else if (shortSignal && OrderVolume > 0m)
{
// Both oscillators crossed down from overbought zones.
_entryPrice = candle.ClosePrice;
SellMarket(OrderVolume);
}
}
else if (Position > 0)
{
ManageLongPosition(candle);
}
else if (Position < 0)
{
ManageShortPosition(candle);
}
_previousDeMarker = deMarker;
_previousStochasticK = currentK;
}
private void ManageLongPosition(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m || _pipSize <= 0m)
return;
var stopLossPrice = entryPrice - HiddenStopLoss * _pipSize;
var takeProfitPrice = entryPrice + HiddenTakeProfit * _pipSize;
// Close long position if the hidden stop or take profit is hit.
if (candle.LowPrice <= stopLossPrice || candle.HighPrice >= takeProfitPrice)
{
SellMarket(Position);
return;
}
var target1 = entryPrice + TargetTp1 * _pipSize;
var target2 = entryPrice + TargetTp2 * _pipSize;
var target3 = entryPrice + TargetTp3 * _pipSize;
if (_longStage < 3 && candle.HighPrice >= target3)
{
var newStop = candle.HighPrice - Math.Max(TargetTp3 - 13m, 0m) * _pipSize;
_longTrailingStop = _longTrailingStop.HasValue ? Math.Max(_longTrailingStop.Value, newStop) : newStop;
_longStage = 3;
return;
}
if (_longStage < 2 && candle.HighPrice >= target2)
{
var newStop = candle.HighPrice - Math.Max(TargetTp2 - 13m, 0m) * _pipSize;
_longTrailingStop = _longTrailingStop.HasValue ? Math.Max(_longTrailingStop.Value, newStop) : newStop;
_longStage = 2;
return;
}
if (_longStage < 1 && candle.HighPrice >= target1)
{
var newStop = candle.HighPrice - Math.Max(TargetTp1 - 13m, 0m) * _pipSize;
_longTrailingStop = _longTrailingStop.HasValue ? Math.Max(_longTrailingStop.Value, newStop) : newStop;
_longStage = 1;
return;
}
// Exit if the trailing stop is touched after at least one target.
if (_longTrailingStop is decimal trailing && candle.LowPrice <= trailing)
{
SellMarket(Position);
}
}
private void ManageShortPosition(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m || _pipSize <= 0m)
return;
var stopLossPrice = entryPrice + HiddenStopLoss * _pipSize;
var takeProfitPrice = entryPrice - HiddenTakeProfit * _pipSize;
// Close short position if hidden protective levels are reached.
if (candle.HighPrice >= stopLossPrice || candle.LowPrice <= takeProfitPrice)
{
BuyMarket(Math.Abs(Position));
return;
}
var target1 = entryPrice - TargetTp1 * _pipSize;
var target2 = entryPrice - TargetTp2 * _pipSize;
var target3 = entryPrice - TargetTp3 * _pipSize;
if (_shortStage < 3 && candle.LowPrice <= target3)
{
var newStop = candle.LowPrice + (TargetTp3 + 13m) * _pipSize;
_shortTrailingStop = _shortTrailingStop.HasValue ? Math.Min(_shortTrailingStop.Value, newStop) : newStop;
_shortStage = 3;
return;
}
if (_shortStage < 2 && candle.LowPrice <= target2)
{
var newStop = candle.LowPrice + (TargetTp2 + 13m) * _pipSize;
_shortTrailingStop = _shortTrailingStop.HasValue ? Math.Min(_shortTrailingStop.Value, newStop) : newStop;
_shortStage = 2;
return;
}
if (_shortStage < 1 && candle.LowPrice <= target1)
{
var newStop = candle.LowPrice + (TargetTp1 + 13m) * _pipSize;
_shortTrailingStop = _shortTrailingStop.HasValue ? Math.Min(_shortTrailingStop.Value, newStop) : newStop;
_shortStage = 1;
return;
}
if (_shortTrailingStop is decimal trailing && candle.HighPrice >= trailing)
{
BuyMarket(Math.Abs(Position));
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0.0001m;
var inverse = (double)(1m / step);
var digits = (int)Math.Round(Math.Log10(inverse));
var adjust = (digits == 3 || digits == 5) ? 10m : 1m;
return step * adjust;
}
}
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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DeMarker, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class cash_machine_5min_legacy_strategy(Strategy):
"""
Cash Machine strategy using DeMarker and Stochastic oscillator crossovers on 5 minute candles.
Gradually tightens a hidden stop when profit targets are reached.
"""
def __init__(self):
super(cash_machine_5min_legacy_strategy, self).__init__()
self._hidden_take_profit = self.Param("HiddenTakeProfit", 60.0) \
.SetDisplay("Hidden Take Profit", "Hidden take profit distance in pips", "Risk")
self._hidden_stop_loss = self.Param("HiddenStopLoss", 30.0) \
.SetDisplay("Hidden Stop Loss", "Hidden stop loss distance in pips", "Risk")
self._target_tp1 = self.Param("TargetTp1", 20.0) \
.SetDisplay("Target TP1", "First profit threshold", "Risk")
self._target_tp2 = self.Param("TargetTp2", 35.0) \
.SetDisplay("Target TP2", "Second profit threshold", "Risk")
self._target_tp3 = self.Param("TargetTp3", 50.0) \
.SetDisplay("Target TP3", "Third profit threshold", "Risk")
self._order_volume = self.Param("OrderVolume", 0.2) \
.SetDisplay("Order Volume", "Order volume for new trades", "Trading")
self._demarker_length = self.Param("DeMarkerLength", 14) \
.SetDisplay("DeMarker Length", "DeMarker averaging period", "Indicators")
self._stochastic_length = self.Param("StochasticLength", 5) \
.SetDisplay("Stochastic Length", "Base Stochastic length", "Indicators")
self._stochastic_k = self.Param("StochasticK", 3) \
.SetDisplay("Stochastic %K", "%K smoothing length", "Indicators")
self._stochastic_d = self.Param("StochasticD", 3) \
.SetDisplay("Stochastic %D", "%D smoothing length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary timeframe", "General")
self._previous_demarker = None
self._previous_stochastic_k = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._long_stage = 0
self._short_stage = 0
self._pip_size = 0.0001
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(cash_machine_5min_legacy_strategy, self).OnReseted()
self._previous_demarker = None
self._previous_stochastic_k = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._long_stage = 0
self._short_stage = 0
self._pip_size = 0.0001
self._entry_price = 0.0
def _calculate_pip_size(self):
step = self.Security.PriceStep if self.Security.PriceStep is not None else 0.0
if step <= 0:
return 0.0001
inverse = 1.0 / step
digits = int(round(math.log10(inverse)))
adjust = 10.0 if digits in (3, 5) else 1.0
return step * adjust
def OnStarted2(self, time):
super(cash_machine_5min_legacy_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
demarker = DeMarker()
demarker.Length = self._demarker_length.Value
stochastic = StochasticOscillator()
stochastic.K.Length = self._stochastic_length.Value
stochastic.D.Length = self._stochastic_d.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(demarker, stochastic, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, demarker)
oscillator_area = self.CreateChartArea()
if oscillator_area is not None:
self.DrawIndicator(oscillator_area, stochastic)
self.DrawOwnTrades(area)
def on_process(self, candle, demarker_value, stochastic_value):
if candle.State != CandleStates.Finished:
return
if not stochastic_value.IsFinal or not self.IsFormedAndOnlineAndAllowTrading():
return
demarker = float(demarker_value)
current_k = stochastic_value.K
if current_k is None:
return
if self.Position == 0:
self._long_stage = 0
self._short_stage = 0
self._long_trailing_stop = None
self._short_trailing_stop = None
if (self.Position == 0 and self._previous_demarker is not None
and self._previous_stochastic_k is not None):
long_signal = (self._previous_demarker < 0.30 and demarker >= 0.30
and self._previous_stochastic_k < 20 and current_k >= 20)
short_signal = (self._previous_demarker > 0.70 and demarker <= 0.70
and self._previous_stochastic_k > 80 and current_k <= 80)
if long_signal and self._order_volume.Value > 0:
self._entry_price = float(candle.ClosePrice)
self.BuyMarket()
elif short_signal and self._order_volume.Value > 0:
self._entry_price = float(candle.ClosePrice)
self.SellMarket()
elif self.Position > 0:
self._manage_long(candle)
elif self.Position < 0:
self._manage_short(candle)
self._previous_demarker = demarker
self._previous_stochastic_k = current_k
def _manage_long(self, candle):
if self._entry_price <= 0 or self._pip_size <= 0:
return
sl_price = self._entry_price - self._hidden_stop_loss.Value * self._pip_size
tp_price = self._entry_price + self._hidden_take_profit.Value * self._pip_size
if float(candle.LowPrice) <= sl_price or float(candle.HighPrice) >= tp_price:
self.SellMarket()
return
t1 = self._entry_price + self._target_tp1.Value * self._pip_size
t2 = self._entry_price + self._target_tp2.Value * self._pip_size
t3 = self._entry_price + self._target_tp3.Value * self._pip_size
if self._long_stage < 3 and float(candle.HighPrice) >= t3:
new_stop = float(candle.HighPrice) - max(self._target_tp3.Value - 13, 0) * self._pip_size
self._long_trailing_stop = max(self._long_trailing_stop, new_stop) if self._long_trailing_stop is not None else new_stop
self._long_stage = 3
return
if self._long_stage < 2 and float(candle.HighPrice) >= t2:
new_stop = float(candle.HighPrice) - max(self._target_tp2.Value - 13, 0) * self._pip_size
self._long_trailing_stop = max(self._long_trailing_stop, new_stop) if self._long_trailing_stop is not None else new_stop
self._long_stage = 2
return
if self._long_stage < 1 and float(candle.HighPrice) >= t1:
new_stop = float(candle.HighPrice) - max(self._target_tp1.Value - 13, 0) * self._pip_size
self._long_trailing_stop = max(self._long_trailing_stop, new_stop) if self._long_trailing_stop is not None else new_stop
self._long_stage = 1
return
if self._long_trailing_stop is not None and float(candle.LowPrice) <= self._long_trailing_stop:
self.SellMarket()
def _manage_short(self, candle):
if self._entry_price <= 0 or self._pip_size <= 0:
return
sl_price = self._entry_price + self._hidden_stop_loss.Value * self._pip_size
tp_price = self._entry_price - self._hidden_take_profit.Value * self._pip_size
if float(candle.HighPrice) >= sl_price or float(candle.LowPrice) <= tp_price:
self.BuyMarket()
return
t1 = self._entry_price - self._target_tp1.Value * self._pip_size
t2 = self._entry_price - self._target_tp2.Value * self._pip_size
t3 = self._entry_price - self._target_tp3.Value * self._pip_size
if self._short_stage < 3 and float(candle.LowPrice) <= t3:
new_stop = float(candle.LowPrice) + (self._target_tp3.Value + 13) * self._pip_size
self._short_trailing_stop = min(self._short_trailing_stop, new_stop) if self._short_trailing_stop is not None else new_stop
self._short_stage = 3
return
if self._short_stage < 2 and float(candle.LowPrice) <= t2:
new_stop = float(candle.LowPrice) + (self._target_tp2.Value + 13) * self._pip_size
self._short_trailing_stop = min(self._short_trailing_stop, new_stop) if self._short_trailing_stop is not None else new_stop
self._short_stage = 2
return
if self._short_stage < 1 and float(candle.LowPrice) <= t1:
new_stop = float(candle.LowPrice) + (self._target_tp1.Value + 13) * self._pip_size
self._short_trailing_stop = min(self._short_trailing_stop, new_stop) if self._short_trailing_stop is not None else new_stop
self._short_stage = 1
return
if self._short_trailing_stop is not None and float(candle.HighPrice) >= self._short_trailing_stop:
self.BuyMarket()
def CreateClone(self):
return cash_machine_5min_legacy_strategy()