MTC Combo v2 策略
来自 MetaTrader 脚本“MTC Combo v2 (barabashkakvn's edition)”的转换。
逻辑
- 通过移动平均线斜率判断趋势。
- 感知器过滤器计算多个间隔的开盘价差的加权和。
Pass选择使用的分支:- 4:
perceptron3 > 0且perceptron2 > 0时做多;perceptron3 <= 0且perceptron1 < 0时做空。 - 3:
perceptron2 > 0时做多。 - 2:
perceptron1 < 0时做空。 - 其他:仅依据 MA 斜率交易。
- 4:
止损与止盈由 Sl*、Tp* 参数给出。
参数
MaPeriod– 移动平均周期。P2、P3、P4– 感知器所用的间隔。Pass– 决策模式。Sl1/Tp1,Sl2/Tp2,Sl3/Tp3– 各分支的止损与止盈。CandleType– 处理的蜡烛类型。
说明
策略一次只持有一笔仓位,满足止损或止盈条件后平仓。
免责声明
仅供学习,不构成投资建议。
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>
/// Simplified MTC Combo v2 strategy.
/// </summary>
public class MtcComboV2Strategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _p2;
private readonly StrategyParam<int> _p3;
private readonly StrategyParam<int> _p4;
private readonly StrategyParam<int> _pass;
private readonly StrategyParam<decimal> _sl1;
private readonly StrategyParam<decimal> _tp1;
private readonly StrategyParam<decimal> _sl2;
private readonly StrategyParam<decimal> _tp2;
private readonly StrategyParam<decimal> _sl3;
private readonly StrategyParam<decimal> _tp3;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _ma;
private decimal? _prevMa;
private readonly Queue<decimal> _opens = new();
private decimal _entry;
private decimal _sl;
private decimal _tp;
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int P2 { get => _p2.Value; set => _p2.Value = value; }
public int P3 { get => _p3.Value; set => _p3.Value = value; }
public int P4 { get => _p4.Value; set => _p4.Value = value; }
public int Pass { get => _pass.Value; set => _pass.Value = value; }
public decimal Sl1 { get => _sl1.Value; set => _sl1.Value = value; }
public decimal Tp1 { get => _tp1.Value; set => _tp1.Value = value; }
public decimal Sl2 { get => _sl2.Value; set => _sl2.Value = value; }
public decimal Tp2 { get => _tp2.Value; set => _tp2.Value = value; }
public decimal Sl3 { get => _sl3.Value; set => _sl3.Value = value; }
public decimal Tp3 { get => _tp3.Value; set => _tp3.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MtcComboV2Strategy()
{
_maPeriod = Param(nameof(MaPeriod), 10).SetGreaterThanZero();
_p2 = Param(nameof(P2), 20).SetGreaterThanZero();
_p3 = Param(nameof(P3), 20).SetGreaterThanZero();
_p4 = Param(nameof(P4), 20).SetGreaterThanZero();
_pass = Param(nameof(Pass), 10);
_sl1 = Param(nameof(Sl1), 50m);
_tp1 = Param(nameof(Tp1), 50m);
_sl2 = Param(nameof(Sl2), 50m);
_tp2 = Param(nameof(Tp2), 50m);
_sl3 = Param(nameof(Sl3), 50m);
_tp3 = Param(nameof(Tp3), 50m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame());
Volume = 1m;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() =>
[(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_ma = default;
_prevMa = null;
_opens.Clear();
_entry = 0m;
_sl = 0m;
_tp = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = new SMA { Length = MaPeriod };
var sub = SubscribeCandles(CandleType);
sub.Bind(_ma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, sub);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
_opens.Enqueue(candle.OpenPrice);
var max = Math.Max(Math.Max(P2, P3), P4) * 4 + 5;
while (_opens.Count > max)
_opens.Dequeue();
if (Position != 0)
{
var step = Security.PriceStep ?? 1m;
var stop = Position > 0 ? _entry - _sl * step : _entry + _sl * step;
var take = Position > 0 ? _entry + _tp * step : _entry - _tp * step;
if ((Position > 0 && candle.LowPrice <= stop) || (Position < 0 && candle.HighPrice >= stop))
{
ClosePos();
return;
}
if ((Position > 0 && candle.HighPrice >= take) || (Position < 0 && candle.LowPrice <= take))
{
ClosePos();
return;
}
}
var slope = _prevMa is null ? 0m : maValue - _prevMa.Value;
_prevMa = maValue;
if (Position != 0)
return;
_sl = Sl1;
_tp = Tp1;
var dir = Supervisor(slope);
if (dir > 0m)
{
BuyMarket();
_entry = candle.ClosePrice;
}
else if (dir < 0m)
{
SellMarket();
_entry = candle.ClosePrice;
}
}
private void ClosePos()
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
}
private decimal Supervisor(decimal slope)
{
if (Pass == 4)
{
if (Perceptron(P4) > 0m && Perceptron(P3) > 0m)
{ _sl = Sl3; _tp = Tp3; return 1m; }
if (Perceptron(P4) <= 0m && Perceptron(P2) < 0m)
{ _sl = Sl2; _tp = Tp2; return -1m; }
}
else if (Pass == 3)
{
if (Perceptron(P3) > 0m)
{ _sl = Sl3; _tp = Tp3; return 1m; }
}
else if (Pass == 2)
{
if (Perceptron(P2) < 0m)
{ _sl = Sl2; _tp = Tp2; return -1m; }
}
return slope;
}
private decimal Perceptron(int p)
{
if (_opens.Count <= p * 4)
return 0m;
var arr = _opens.ToArray();
var len = arr.Length;
var a1 = arr[len - 1] - arr[len - 1 - p];
var a2 = arr[len - 1 - p] - arr[len - 1 - p * 2];
var a3 = arr[len - 1 - p * 2] - arr[len - 1 - p * 3];
var a4 = arr[len - 1 - p * 3] - arr[len - 1 - p * 4];
return a1 + a2 + a3 + a4;
}
}
import clr
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mtc_combo_v2_strategy(Strategy):
"""
MTC Combo v2: SMA slope + perceptron-based direction with manual SL/TP.
"""
def __init__(self):
super(mtc_combo_v2_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 10).SetDisplay("MA Period", "SMA period", "Indicators")
self._p2 = self.Param("P2", 20).SetDisplay("P2", "Perceptron P2", "Signals")
self._p3 = self.Param("P3", 20).SetDisplay("P3", "Perceptron P3", "Signals")
self._p4 = self.Param("P4", 20).SetDisplay("P4", "Perceptron P4", "Signals")
self._pass_val = self.Param("Pass", 10).SetDisplay("Pass", "Strategy pass", "Signals")
self._sl1 = self.Param("Sl1", 50.0).SetDisplay("SL1", "Stop loss 1", "Risk")
self._tp1 = self.Param("Tp1", 50.0).SetDisplay("TP1", "Take profit 1", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candles", "General")
self._prev_ma = None
self._opens = []
self._entry = 0.0
self._sl = 50.0
self._tp = 50.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mtc_combo_v2_strategy, self).OnReseted()
self._prev_ma = None
self._opens = []
self._entry = 0.0
self._sl = float(self._sl1.Value)
self._tp = float(self._tp1.Value)
def OnStarted2(self, time):
super(mtc_combo_v2_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
ma = float(ma_val)
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
self._opens.append(open_p)
max_len = max(self._p2.Value, self._p3.Value, self._p4.Value) * 4 + 5
while len(self._opens) > max_len:
self._opens.pop(0)
if self.Position != 0:
step = 1.0
stop = self._entry - self._sl * step if self.Position > 0 else self._entry + self._sl * step
take = self._entry + self._tp * step if self.Position > 0 else self._entry - self._tp * step
if (self.Position > 0 and low <= stop) or (self.Position < 0 and high >= stop):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
return
if (self.Position > 0 and high >= take) or (self.Position < 0 and low <= take):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
return
slope = ma - self._prev_ma if self._prev_ma is not None else 0.0
self._prev_ma = ma
if self.Position != 0:
return
self._sl = float(self._sl1.Value)
self._tp = float(self._tp1.Value)
direction = self._supervisor(slope)
if direction > 0:
self.BuyMarket()
self._entry = close
elif direction < 0:
self.SellMarket()
self._entry = close
def _supervisor(self, slope):
p = self._pass_val.Value
if p == 4:
if self._perceptron(self._p4.Value) > 0 and self._perceptron(self._p3.Value) > 0:
return 1.0
if self._perceptron(self._p4.Value) <= 0 and self._perceptron(self._p2.Value) < 0:
return -1.0
elif p == 3:
if self._perceptron(self._p3.Value) > 0:
return 1.0
elif p == 2:
if self._perceptron(self._p2.Value) < 0:
return -1.0
return slope
def _perceptron(self, p):
if len(self._opens) <= p * 4:
return 0.0
arr = self._opens
n = len(arr)
a1 = arr[n - 1] - arr[n - 1 - p]
a2 = arr[n - 1 - p] - arr[n - 1 - p * 2]
a3 = arr[n - 1 - p * 2] - arr[n - 1 - p * 3]
a4 = arr[n - 1 - p * 3] - arr[n - 1 - p * 4]
return a1 + a2 + a3 + a4
def CreateClone(self):
return mtc_combo_v2_strategy()