Simple Trading System 策略
该策略复刻自 MetaTrader 的 Simple Trading System。它使用向前偏移的移动平均线,并将当前收盘价与过去的收盘价比较,以捕捉短期趋势反转。当移动平均线低于 MaShift 根之前的值,且收盘价位于 MaShift 和 MaPeriod + MaShift 根之前的收盘价之间,并且当前 K 线为阴线时,生成买入信号。卖出信号则相反。根据参数设置,策略在出现信号时可以开仓和/或平仓多头或空头。可选的止损和止盈参数用于风险控制。
细节
- 入场条件:
- 做多:
MA(t) <= MA(t+MaShift)&&Close(t) >= Close(t+MaShift)&&Close(t) <= Close(t+MaPeriod+MaShift)&&Close(t) < Open(t) - 做空:
MA(t) >= MA(t+MaShift)&&Close(t) <= Close(t+MaShift)&&Close(t) >= Close(t+MaPeriod+MaShift)&&Close(t) > Open(t)
- 做多:
- 多空方向:根据
BuyPositionOpen和SellPositionOpen可同时操作多头和空头。 - 离场条件:若启用
BuyPositionClose或SellPositionClose,则相反信号会平掉已有仓位。 - 止损/止盈:可选,通过
StopLoss和TakeProfit以绝对价格设置。 - 默认值:
MaType= EMAMaPeriod= 2MaShift= 4PriceType= CloseCandleType= 6 小时TakeProfit= 2000StopLoss= 1000Volume= 1
- 过滤器:
- 类型:趋势跟随
- 方向:双向
- 指标:移动平均线
- 止损:支持
- 复杂度:中等
- 时间框架:中期
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// Simple trading system based on shifted moving average and price comparisons.
/// Generates buy and sell signals when the current close meets several conditions
/// relative to previous closes and the moving average.
/// </summary>
public class SimpleTradingSystemStrategy : Strategy
{
public enum MovingAverageTypes
{
/// <summary>
/// Simple Moving Average (SMA).
/// </summary>
SMA,
/// <summary>
/// Exponential Moving Average (EMA).
/// </summary>
EMA,
/// <summary>
/// Double Exponential Moving Average (DEMA).
/// </summary>
DEMA,
/// <summary>
/// Triple Exponential Moving Average (TEMA).
/// </summary>
TEMA,
/// <summary>
/// Weighted Moving Average (WMA).
/// </summary>
WMA,
/// <summary>
/// Volume Weighted Moving Average (VWMA).
/// </summary>
VWMA
}
public enum PriceTypes
{
/// <summary>
/// Close price.
/// </summary>
Close,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Open price.
/// </summary>
Open,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// Typical price (HL + C) / 3.
/// </summary>
Typical,
/// <summary>
/// Center price (HL) / 2.
/// </summary>
Center
}
private readonly StrategyParam<MovingAverageTypes> _maType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _maShift;
private readonly StrategyParam<PriceTypes> _priceType;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private IIndicator _ma;
private decimal[] _maBuffer = Array.Empty<decimal>();
private decimal[] _closeBuffer = Array.Empty<decimal>();
private int _sign;
private int _barsSinceTrade;
/// <summary>
/// Moving average type.
/// </summary>
public MovingAverageTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Shift applied when comparing previous values.
/// </summary>
public int MaShift
{
get => _maShift.Value;
set => _maShift.Value = value;
}
/// <summary>
/// Price type for the moving average.
/// </summary>
public PriceTypes PriceType
{
get => _priceType.Value;
set => _priceType.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPositionOpen
{
get => _buyOpen.Value;
set => _buyOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPositionOpen
{
get => _sellOpen.Value;
set => _sellOpen.Value = value;
}
/// <summary>
/// Allow closing long positions on sell signal.
/// </summary>
public bool BuyPositionClose
{
get => _buyClose.Value;
set => _buyClose.Value = value;
}
/// <summary>
/// Allow closing short positions on buy signal.
/// </summary>
public bool SellPositionClose
{
get => _sellClose.Value;
set => _sellClose.Value = value;
}
/// <summary>
/// Take profit in absolute price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss in absolute price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes <see cref="SimpleTradingSystemStrategy"/>.
/// </summary>
public SimpleTradingSystemStrategy()
{
_maType = Param(nameof(MaType), MovingAverageTypes.EMA)
.SetDisplay("MA Type", "Moving average type", "Parameters");
_maPeriod = Param(nameof(MaPeriod), 4)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average period", "Parameters")
;
_maShift = Param(nameof(MaShift), 4)
.SetNotNegative()
.SetDisplay("MA Shift", "Shift for comparisons", "Parameters")
;
_priceType = Param(nameof(PriceType), PriceTypes.Close)
.SetDisplay("Price Type", "Source price for MA", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(6).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_buyOpen = Param(nameof(BuyPositionOpen), true)
.SetDisplay("Buy Open", "Allow opening long positions", "Trading");
_sellOpen = Param(nameof(SellPositionOpen), true)
.SetDisplay("Sell Open", "Allow opening short positions", "Trading");
_buyClose = Param(nameof(BuyPositionClose), true)
.SetDisplay("Buy Close", "Allow closing longs on sell signal", "Trading");
_sellClose = Param(nameof(SellPositionClose), true)
.SetDisplay("Sell Close", "Allow closing shorts on buy signal", "Trading");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = null;
_maBuffer = Array.Empty<decimal>();
_closeBuffer = Array.Empty<decimal>();
_sign = 0;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = CreateMa(MaType, MaPeriod);
_maBuffer = new decimal[MaShift + 1];
_closeBuffer = new decimal[MaPeriod + MaShift + 1];
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetPrice(candle);
var maValue = _ma!.Process(new DecimalIndicatorValue(_ma, price, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
Shift(_maBuffer, maValue);
Shift(_closeBuffer, candle.ClosePrice);
if (_closeBuffer[0] == 0m)
return; // not enough history yet
var ma0 = _maBuffer[_maBuffer.Length - 1];
var ma1 = _maBuffer[_maBuffer.Length - 1 - MaShift];
var close = _closeBuffer[_closeBuffer.Length - 1];
var closeShift = _closeBuffer[_closeBuffer.Length - 1 - MaShift];
var closeSum = _closeBuffer[0];
var open = candle.OpenPrice;
var buySignal = _sign < 1 && ma0 <= ma1 && close >= closeShift && close <= closeSum && close < open;
var sellSignal = _sign > -1 && ma0 >= ma1 && close <= closeShift && close >= closeSum && close > open;
if (_barsSinceTrade >= CooldownBars && buySignal)
{
if (BuyPositionOpen && IsFormedAndOnlineAndAllowTrading() && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (SellPositionClose && Position < 0)
BuyMarket(Math.Abs(Position));
_sign = 1;
_barsSinceTrade = 0;
}
else if (_barsSinceTrade >= CooldownBars && sellSignal)
{
if (SellPositionOpen && IsFormedAndOnlineAndAllowTrading() && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (BuyPositionClose && Position > 0)
SellMarket(Math.Abs(Position));
_sign = -1;
_barsSinceTrade = 0;
}
}
private static void Shift(decimal[] array, decimal value)
{
for (var i = 0; i < array.Length - 1; i++)
array[i] = array[i + 1];
array[array.Length - 1] = value;
}
private decimal GetPrice(ICandleMessage candle)
{
return PriceType switch
{
PriceTypes.Close => candle.ClosePrice,
PriceTypes.High => candle.HighPrice,
PriceTypes.Open => candle.OpenPrice,
PriceTypes.Low => candle.LowPrice,
PriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
PriceTypes.Center => (candle.HighPrice + candle.LowPrice) / 2m,
_ => candle.ClosePrice
};
}
private static IIndicator CreateMa(MovingAverageTypes type, int length)
{
return type switch
{
MovingAverageTypes.SMA => new SimpleMovingAverage { Length = length },
MovingAverageTypes.EMA => new ExponentialMovingAverage { Length = length },
MovingAverageTypes.DEMA => new DoubleExponentialMovingAverage { Length = length },
MovingAverageTypes.TEMA => new TripleExponentialMovingAverage { Length = length },
MovingAverageTypes.WMA => new WeightedMovingAverage { Length = length },
MovingAverageTypes.VWMA => new VolumeWeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length }
};
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage,
DoubleExponentialMovingAverage, TripleExponentialMovingAverage,
WeightedMovingAverage, VolumeWeightedMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
# MA type constants
MA_SMA = 0
MA_EMA = 1
MA_DEMA = 2
MA_TEMA = 3
MA_WMA = 4
MA_VWMA = 5
# Price type constants
PRICE_CLOSE = 0
PRICE_HIGH = 1
PRICE_OPEN = 2
PRICE_LOW = 3
PRICE_TYPICAL = 4
PRICE_CENTER = 5
class simple_trading_system_strategy(Strategy):
def __init__(self):
super(simple_trading_system_strategy, self).__init__()
self._ma_type = self.Param("MaType", MA_EMA) \
.SetDisplay("MA Type", "Moving average type", "Parameters")
self._ma_period = self.Param("MaPeriod", 4) \
.SetDisplay("MA Period", "Moving average period", "Parameters")
self._ma_shift = self.Param("MaShift", 4) \
.SetDisplay("MA Shift", "Shift for comparisons", "Parameters")
self._price_type = self.Param("PriceType", PRICE_CLOSE) \
.SetDisplay("Price Type", "Source price for MA", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(6))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._buy_open = self.Param("BuyPositionOpen", True) \
.SetDisplay("Buy Open", "Allow opening long positions", "Trading")
self._sell_open = self.Param("SellPositionOpen", True) \
.SetDisplay("Sell Open", "Allow opening short positions", "Trading")
self._buy_close = self.Param("BuyPositionClose", True) \
.SetDisplay("Buy Close", "Allow closing longs on sell signal", "Trading")
self._sell_close = self.Param("SellPositionClose", True) \
.SetDisplay("Sell Close", "Allow closing shorts on buy signal", "Trading")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Take profit in price units", "Risk")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
self._ma = None
self._ma_buffer = []
self._close_buffer = []
self._sign = 0
self._bars_since_trade = 0
@property
def MaType(self):
return self._ma_type.Value
@MaType.setter
def MaType(self, value):
self._ma_type.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def MaShift(self):
return self._ma_shift.Value
@MaShift.setter
def MaShift(self, value):
self._ma_shift.Value = value
@property
def PriceType(self):
return self._price_type.Value
@PriceType.setter
def PriceType(self, value):
self._price_type.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BuyPositionOpen(self):
return self._buy_open.Value
@BuyPositionOpen.setter
def BuyPositionOpen(self, value):
self._buy_open.Value = value
@property
def SellPositionOpen(self):
return self._sell_open.Value
@SellPositionOpen.setter
def SellPositionOpen(self, value):
self._sell_open.Value = value
@property
def BuyPositionClose(self):
return self._buy_close.Value
@BuyPositionClose.setter
def BuyPositionClose(self, value):
self._buy_close.Value = value
@property
def SellPositionClose(self):
return self._sell_close.Value
@SellPositionClose.setter
def SellPositionClose(self, value):
self._sell_close.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
def _create_ma(self, ma_type, length):
if ma_type == MA_SMA:
ma = SimpleMovingAverage()
elif ma_type == MA_EMA:
ma = ExponentialMovingAverage()
elif ma_type == MA_DEMA:
ma = DoubleExponentialMovingAverage()
elif ma_type == MA_TEMA:
ma = TripleExponentialMovingAverage()
elif ma_type == MA_WMA:
ma = WeightedMovingAverage()
elif ma_type == MA_VWMA:
ma = VolumeWeightedMovingAverage()
else:
ma = SimpleMovingAverage()
ma.Length = length
return ma
def _get_price(self, candle):
pt = self.PriceType
if pt == PRICE_CLOSE:
return float(candle.ClosePrice)
elif pt == PRICE_HIGH:
return float(candle.HighPrice)
elif pt == PRICE_OPEN:
return float(candle.OpenPrice)
elif pt == PRICE_LOW:
return float(candle.LowPrice)
elif pt == PRICE_TYPICAL:
return (float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 3.0
elif pt == PRICE_CENTER:
return (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
else:
return float(candle.ClosePrice)
def OnStarted2(self, time):
super(simple_trading_system_strategy, self).OnStarted2(time)
self._ma = self._create_ma(self.MaType, self.MaPeriod)
ma_shift = self.MaShift
self._ma_buffer = [0.0] * (ma_shift + 1)
self._close_buffer = [0.0] * (self.MaPeriod + ma_shift + 1)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ma)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(self.TakeProfit, UnitTypes.Absolute),
stopLoss=Unit(self.StopLoss, UnitTypes.Absolute))
def _shift_buffer(self, buf, value):
for i in range(len(buf) - 1):
buf[i] = buf[i + 1]
buf[len(buf) - 1] = value
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price = self._get_price(candle)
ma_result = process_float(self._ma, price, candle.OpenTime, True)
ma_value = float(ma_result)
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
self._shift_buffer(self._ma_buffer, ma_value)
self._shift_buffer(self._close_buffer, float(candle.ClosePrice))
if self._close_buffer[0] == 0.0:
return
ma_shift = self.MaShift
ma0 = self._ma_buffer[len(self._ma_buffer) - 1]
ma1 = self._ma_buffer[len(self._ma_buffer) - 1 - ma_shift]
close = self._close_buffer[len(self._close_buffer) - 1]
close_shift = self._close_buffer[len(self._close_buffer) - 1 - ma_shift]
close_sum = self._close_buffer[0]
open_price = float(candle.OpenPrice)
buy_signal = (self._sign < 1 and ma0 <= ma1
and close >= close_shift and close <= close_sum
and close < open_price)
sell_signal = (self._sign > -1 and ma0 >= ma1
and close <= close_shift and close >= close_sum
and close > open_price)
if self._bars_since_trade >= self.CooldownBars and buy_signal:
if self.BuyPositionOpen and self.IsFormedAndOnlineAndAllowTrading() and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self.SellPositionClose and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._sign = 1
self._bars_since_trade = 0
elif self._bars_since_trade >= self.CooldownBars and sell_signal:
if self.SellPositionOpen and self.IsFormedAndOnlineAndAllowTrading() and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.BuyPositionClose and self.Position > 0:
self.SellMarket(abs(self.Position))
self._sign = -1
self._bars_since_trade = 0
def OnReseted(self):
super(simple_trading_system_strategy, self).OnReseted()
self._ma = None
self._ma_buffer = []
self._close_buffer = []
self._sign = 0
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return simple_trading_system_strategy()