首页
/
策略示例
在 GitHub 上查看
移动平均与帧
移植自 MetaTrader 5 专家顾问 “Moving Average with Frames” 。原版通过比较每根 K 线的开盘价和收盘价与向前平移的简单移动平均线(SMA)来生成信号,并在优化后绘制多条权益曲线。本移植版聚焦交易逻辑:仅在每根完成的 K 线后响应,在净额制度下维持单一仓位,并完整保留原策略的资金管理规则。
交易逻辑
数据来源 :订阅参数 CandleType 指定的时间框架,只处理已经收盘的蜡烛,复刻 MQL5 中的 if(rt[1].tick_volume>1) return; 限制。
指标 :使用周期为 MovingPeriod 的简单移动平均线。通过内部缓冲区将指标值向前平移 MovingShift 根已完成的蜡烛。
预热阶段 :在累积到至少 100 根完成的蜡烛之前禁止交易,对应原程序的 Bars(_Symbol,_Period)>100 检查。
入场条件
做多 :蜡烛开盘价低于平移后的 SMA,收盘价高于 SMA。
做空 :蜡烛开盘价高于平移后的 SMA,收盘价低于 SMA。
策略始终只持有一个方向的净仓,信号反转时先平掉旧仓再开新仓。
离场条件 :多头在价格重新跌破平移 SMA 时平仓,空头在价格重新上穿平移 SMA 时平仓。与原策略一致,同一根 K 线不会在平仓后立刻重新开仓。
风险与仓位控制
MaximumRisk :当投资组合市值可用时,按照 Portfolio.CurrentValue * MaximumRisk / price 估算下单量;否则回退到手动设置的 Volume。
DecreaseFactor :连续超过一次亏损后,将下一笔下单量减少 volume * losses / DecreaseFactor,完全复刻 MetaTrader 的“递减手数”逻辑;盈利交易会清零计数。
数量规范化 :计算出的下单量会按照合约的 VolumeStep 对齐,并限制在 MinVolume 与 MaxVolume 之间;若没有成交量步长信息,则四舍五入保留两位小数。
其他说明
MetaTrader 的“帧”可视化未迁移,StockSharp 已提供完善的优化分析工具;本移植保持了信号与资金管理的一致性。
指标数值通过 Bind 回调直接获取,没有调用 GetValue。
连续亏损统计放在 OnOwnTradeReceived 中,实现对部分成交和净额模式的正确处理。
参数
参数
默认值
说明
MaximumRisk
0.02
每次建仓所风险的账户权益比例。
DecreaseFactor
3
当出现连续亏损时用于缩减手数的除数。
MovingPeriod
12
计算简单移动平均的周期(使用收盘价)。
MovingShift
6
将 SMA 向前平移的已完成蜡烛数量。
CandleType
1 小时时间框架
策略订阅并处理的主蜡烛序列。
使用建议
在 StockSharp Designer 或代码中将策略绑定到目标证券与投资组合。
根据原 MT5 图表调整 CandleType,确保时间框架一致。
按账户规模与风险承受能力微调 MaximumRisk 与 DecreaseFactor。
通过回测验证交叉信号与原专家顾问的表现是否一致。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving Average With Frames: SMA crossover with candle body confirmation.
/// Buys when close crosses above shifted SMA, sells when crosses below.
/// </summary>
public class MovingAverageWithFramesStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _prevClose;
public MovingAverageWithFramesStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 12)
.SetDisplay("SMA Length", "SMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevClose = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
if (_prevClose == 0)
{
_prevClose = close;
return;
}
if (Position > 0)
{
if (close < smaVal && _prevClose >= smaVal)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < smaVal && _prevClose >= smaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevClose = close;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
class moving_average_with_frames_strategy(Strategy):
def __init__(self):
super(moving_average_with_frames_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._sma_length = self.Param("SmaLength", 12) \
.SetDisplay("SMA Length", "SMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmaLength(self):
return self._sma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(moving_average_with_frames_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_close = 0.0
self._sma = SimpleMovingAverage()
self._sma.Length = self.SmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sv = float(sma_val)
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
return
if self.Position > 0:
if close < sv and self._prev_close >= sv:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close > sv and self._prev_close <= sv:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > sv and self._prev_close <= sv:
self._entry_price = close
self.BuyMarket()
elif close < sv and self._prev_close >= sv:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(moving_average_with_frames_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
def CreateClone(self):
return moving_average_with_frames_strategy()