Cash Machine 5min 策略
概述
该策略是将 CashMachine 5min MQL 智能交易系统直接迁移到 StockSharp 高级 API 的结果。策略基于 5 分钟K线,结合 DeMarker 指标与随机指标(Stochastic)的交叉过滤,并使用隐藏止损/止盈加分段移动止损来管理仓位,力图在趋势发展时逐步锁定利润。
交易逻辑
入场条件
- 做多:上一个 DeMarker 值低于 0.30,当前值大于或等于 0.30,且同一根K线上随机指标 %K 从下向上穿越 20。必须没有持仓。
- 做空:上一个 DeMarker 值高于 0.70,当前值小于或等于 0.70,且随机指标 %K 从上向下穿越 80。必须没有持仓。
仓位管理
- 同一时间只允许持有一个方向的仓位;在持仓期间忽略反向信号。
- 当价格触及
Entry ± HiddenStopLoss或Entry ± HiddenTakeProfit(以点数表示)时触发隐藏止损/止盈并平仓。 - 三个利润目标(
TargetTp1/2/3)会将隐藏移动止损调整至:多头为当前价格 - (target - 13)点,空头为当前价格 + (target + 13)点。额外的 13 点用于复现原始 EA 的保护逻辑,在达到阶段目标后锁定收益但不过早平仓。 - 激活移动止损后,一旦价格触碰该水平即以市价离场。
指标
- DeMarker:用于识别动能反转;周期与原始脚本一致。
- 随机指标 (Stochastic Oscillator):使用原始的 %K 周期 (
StochasticLength)、%K 平滑 (StochasticK) 以及 %D 平滑 (StochasticD) 设置。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
HiddenTakeProfit |
隐藏止盈距离(点)。 | 60 |
HiddenStopLoss |
隐藏止损距离(点)。 | 30 |
TargetTp1 |
第一级移动止损触发点(点)。 | 20 |
TargetTp2 |
第二级移动止损触发点(点)。 | 35 |
TargetTp3 |
第三级移动止损触发点(点)。 | 50 |
DeMarkerLength |
DeMarker 平均周期。 | 14 |
StochasticLength |
随机指标 %K 回看周期。 | 5 |
StochasticK |
%K 平滑周期。 | 3 |
StochasticD |
%D 平滑周期。 | 3 |
CandleType |
计算所用的K线类型(默认 5 分钟)。 | 5 分钟周期 |
备注
- 点值通过
Security.PriceStep计算;若无法获得价格步长,则使用默认值0.0001,与原 EA 在 3/5 位报价上的行为保持一致。 - 所有决策基于已完成的K线。原策略在每个 tick 上运行,因此在盘中细节上可能存在轻微差异。
- 下单量使用 StockSharp 的
Strategy.Volume属性控制,可根据需求自行设置。
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: combines DeMarker and Stochastic oscillator crossovers
/// with staged profit protection using ATR-based distances.
/// </summary>
public class CashMachine5minStrategy : Strategy
{
private readonly StrategyParam<decimal> _tpAtrMult;
private readonly StrategyParam<decimal> _slAtrMult;
private readonly StrategyParam<decimal> _trailAtrMult;
private readonly StrategyParam<int> _deMarkerLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevDeMarker;
private decimal? _prevRsi;
private decimal _entryPrice;
private decimal? _stopPrice;
public decimal TpAtrMult { get => _tpAtrMult.Value; set => _tpAtrMult.Value = value; }
public decimal SlAtrMult { get => _slAtrMult.Value; set => _slAtrMult.Value = value; }
public decimal TrailAtrMult { get => _trailAtrMult.Value; set => _trailAtrMult.Value = value; }
public int DeMarkerLength { get => _deMarkerLength.Value; set => _deMarkerLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public CashMachine5minStrategy()
{
_tpAtrMult = Param(nameof(TpAtrMult), 2.5m)
.SetGreaterThanZero()
.SetDisplay("TP ATR Mult", "Take profit ATR multiplier", "Risk");
_slAtrMult = Param(nameof(SlAtrMult), 1.5m)
.SetGreaterThanZero()
.SetDisplay("SL ATR Mult", "Stop loss ATR multiplier", "Risk");
_trailAtrMult = Param(nameof(TrailAtrMult), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Trail ATR Mult", "Trailing stop ATR multiplier", "Risk");
_deMarkerLength = Param(nameof(DeMarkerLength), 14)
.SetGreaterThanZero()
.SetDisplay("DeMarker Length", "DeMarker period", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR calculation period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevDeMarker = null;
_prevRsi = null;
_entryPrice = 0;
_stopPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var deMarker = new DeMarker { Length = DeMarkerLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(deMarker, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, deMarker);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal deMarker, decimal rsi, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
// Manage position
if (Position > 0)
{
// Trailing stop
var trail = close - TrailAtrMult * atr;
if (_stopPrice == null || trail > _stopPrice)
_stopPrice = trail;
// Take profit
var tp = _entryPrice + TpAtrMult * atr;
if (close <= _stopPrice || close >= tp)
{
SellMarket(Math.Abs(Position));
_stopPrice = null;
_entryPrice = 0;
}
}
else if (Position < 0)
{
var trail = close + TrailAtrMult * atr;
if (_stopPrice == null || trail < _stopPrice)
_stopPrice = trail;
var tp = _entryPrice - TpAtrMult * atr;
if (close >= _stopPrice || close <= tp)
{
BuyMarket(Math.Abs(Position));
_stopPrice = null;
_entryPrice = 0;
}
}
// Entry signals
if (Position == 0 && _prevDeMarker is decimal prevDe && _prevRsi is decimal prevRsi)
{
var longSignal = (prevDe < 0.25m && deMarker >= 0.25m) || (prevRsi < 25m && rsi >= 25m);
var shortSignal = (prevDe > 0.75m && deMarker <= 0.75m) || (prevRsi > 75m && rsi <= 75m);
if (longSignal)
{
BuyMarket();
_entryPrice = close;
_stopPrice = close - SlAtrMult * atr;
}
else if (shortSignal)
{
SellMarket();
_entryPrice = close;
_stopPrice = close + SlAtrMult * atr;
}
}
_prevDeMarker = deMarker;
_prevRsi = rsi;
}
}
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 DeMarker, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class cash_machine5min_strategy(Strategy):
def __init__(self):
super(cash_machine5min_strategy, self).__init__()
self._tp_atr_mult = self.Param("TpAtrMult", 2.5)
self._sl_atr_mult = self.Param("SlAtrMult", 1.5)
self._trail_atr_mult = self.Param("TrailAtrMult", 1.0)
self._de_marker_length = self.Param("DeMarkerLength", 14)
self._rsi_length = self.Param("RsiLength", 14)
self._atr_length = self.Param("AtrLength", 14)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_de_marker = None
self._prev_rsi = None
self._entry_price = 0.0
self._stop_price = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(cash_machine5min_strategy, self).OnStarted2(time)
self._prev_de_marker = None
self._prev_rsi = None
self._entry_price = 0.0
self._stop_price = None
de_marker = DeMarker()
de_marker.Length = self._de_marker_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(de_marker, rsi, atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, de_marker_value, rsi_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
de_marker = float(de_marker_value)
rsi = float(rsi_value)
atr = float(atr_value)
close = float(candle.ClosePrice)
tp_mult = float(self._tp_atr_mult.Value)
sl_mult = float(self._sl_atr_mult.Value)
trail_mult = float(self._trail_atr_mult.Value)
pos = float(self.Position)
if pos > 0:
trail = close - trail_mult * atr
if self._stop_price is None or trail > self._stop_price:
self._stop_price = trail
tp = self._entry_price + tp_mult * atr
if close <= self._stop_price or close >= tp:
self.SellMarket(abs(pos))
self._stop_price = None
self._entry_price = 0.0
elif pos < 0:
trail = close + trail_mult * atr
if self._stop_price is None or trail < self._stop_price:
self._stop_price = trail
tp = self._entry_price - tp_mult * atr
if close >= self._stop_price or close <= tp:
self.BuyMarket(abs(pos))
self._stop_price = None
self._entry_price = 0.0
if self.Position == 0 and self._prev_de_marker is not None and self._prev_rsi is not None:
long_signal = (self._prev_de_marker < 0.25 and de_marker >= 0.25) or (self._prev_rsi < 25.0 and rsi >= 25.0)
short_signal = (self._prev_de_marker > 0.75 and de_marker <= 0.75) or (self._prev_rsi > 75.0 and rsi <= 75.0)
if long_signal:
self.BuyMarket()
self._entry_price = close
self._stop_price = close - sl_mult * atr
elif short_signal:
self.SellMarket()
self._entry_price = close
self._stop_price = close + sl_mult * atr
self._prev_de_marker = de_marker
self._prev_rsi = rsi
def OnReseted(self):
super(cash_machine5min_strategy, self).OnReseted()
self._prev_de_marker = None
self._prev_rsi = None
self._entry_price = 0.0
self._stop_price = None
def CreateClone(self):
return cash_machine5min_strategy()