在 GitHub 上查看
Exp TEMA 策略
Exp TEMA 策略是 MetaTrader 专家顾问 Exp_TEMA.mq5 的 StockSharp 版本。原始 EA 会同时监控多只外汇货币对,通过 Triple Exponential Moving Average (TEMA) 的斜率方向来决定开仓与平仓。本次移植保持相同的指标逻辑,不过在 StockSharp 中一次聚焦于一个指定的证券。
交易逻辑
策略只处理 CandleType 参数产生的收盘 K 线。在每根 K 线结束时计算周期为 TemaPeriod 的 TEMA,并使用最近三根的指标值重建原版的斜率判断:
tema[0] 为当前 K 线值,tema[1] 为上一根,tema[2] 为前两根。
- 最近的斜率为
d1 = tema[1] - tema[2],较旧的斜率为 d2 = tema[2] - tema[3]。
- 当斜率向上翻转时触发 做多 (
d2 < 0 且 d1 > 0)。先平掉空头,再以 Volume + |Position| 的数量市价买入。
- 当斜率向下翻转时触发 做空 (
d2 > 0 且 d1 < 0)。先平掉多头,再以 Volume + |Position| 的数量市价卖出。
- 保护性退出遵循原始 EA 的规则:若当前斜率为负,则平掉多头;若当前斜率为正,则平掉空头。
因此,策略在不访问原始缓冲区的情况下复现了相同的信号时机,并完全符合 StockSharp 的高层 API 要求。
参数说明
| 参数 |
默认值 |
说明 |
TemaPeriod |
15 |
Triple Exponential Moving Average 的计算周期。 |
TradeVolume |
1 |
基础下单手数。反手时实际下单量为 TradeVolume + |Position|。 |
StopLossPoints |
1000 |
止损距离(价格步长数量)。若大于零则传入 StartProtection。 |
TakeProfitPoints |
2000 |
止盈距离(价格步长数量)。若大于零则传入 StartProtection。 |
CandleType |
15 分钟 |
用于计算指标的 K 线类型,请选择与原始 EA 图表一致的周期。 |
所有参数均通过 StrategyParam<T> 创建,可在 Designer 中进行优化。
与 MQL5 版本的差异
- 原版 EA 可以一次处理最多 12 个品种。StockSharp 策略绑定到单一
Security,因此此移植版一次交易一个品种。若需要多品种,可同时运行多个策略实例。
- 下单与风控使用
BuyMarket、SellMarket 以及 StartProtection,对应原策略的市价单、止损与止盈,但实现方式遵循 StockSharp 高层 API。
- 指标数据通过
SubscribeCandles().Bind(...) 获取,无需手动复制缓冲区,符合仓库的编码规范。
使用建议
- 为策略指定目标证券,并将
CandleType 调整为与分析时间框架一致。
- 根据品种波动性调整
StopLossPoints 与 TakeProfitPoints 的步长距离。
- 可选地对
TemaPeriod、StopLossPoints、TakeProfitPoints 进行参数优化,以复现 MetaTrader 中的调参流程。
- 利用自动创建的图表区域观察价格、TEMA 曲线以及实际成交。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades TEMA slope reversals from the original Exp_TEMA expert advisor.
/// Enters long when TEMA slope turns positive, short when negative.
/// </summary>
public class ExpTemaStrategy : Strategy
{
private readonly StrategyParam<int> _temaPeriod;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _tema;
private decimal? _prev1;
private decimal? _prev2;
private decimal? _prev3;
public int TemaPeriod
{
get => _temaPeriod.Value;
set => _temaPeriod.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public ExpTemaStrategy()
{
_temaPeriod = Param(nameof(TemaPeriod), 40)
.SetGreaterThanZero()
.SetDisplay("TEMA Period", "Length of Triple Exponential Moving Average", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for TEMA calculation", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_tema = new ExponentialMovingAverage { Length = TemaPeriod };
_prev1 = null;
_prev2 = null;
_prev3 = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_tema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _tema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal temaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_tema.IsFormed)
{
_prev1 = temaValue;
return;
}
if (_prev1 is null)
{
_prev1 = temaValue;
return;
}
if (_prev2 is null)
{
_prev2 = _prev1;
_prev1 = temaValue;
return;
}
if (_prev3 is null)
{
_prev3 = _prev2;
_prev2 = _prev1;
_prev1 = temaValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var dtema1 = _prev1.Value - _prev2.Value;
var dtema2 = _prev2.Value - _prev3.Value;
// Entry on slope reversal
var turnedUp = dtema2 < 0 && dtema1 > 0;
var turnedDown = dtema2 > 0 && dtema1 < 0;
if (turnedUp && Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (turnedDown && Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prev3 = _prev2;
_prev2 = _prev1;
_prev1 = temaValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_tema = null;
_prev1 = null;
_prev2 = null;
_prev3 = null;
base.OnReseted();
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_tema_strategy(Strategy):
def __init__(self):
super(exp_tema_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._tema_period = self.Param("TemaPeriod", 40)
self._prev1 = None
self._prev2 = None
self._prev3 = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TemaPeriod(self):
return self._tema_period.Value
@TemaPeriod.setter
def TemaPeriod(self, value):
self._tema_period.Value = value
def OnReseted(self):
super(exp_tema_strategy, self).OnReseted()
self._prev1 = None
self._prev2 = None
self._prev3 = None
def OnStarted2(self, time):
super(exp_tema_strategy, self).OnStarted2(time)
self._prev1 = None
self._prev2 = None
self._prev3 = None
tema = ExponentialMovingAverage()
tema.Length = self.TemaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(tema, self._process_candle).Start()
def _process_candle(self, candle, tema_value):
if candle.State != CandleStates.Finished:
return
val = float(tema_value)
if self._prev1 is None:
self._prev1 = val
return
if self._prev2 is None:
self._prev2 = self._prev1
self._prev1 = val
return
if self._prev3 is None:
self._prev3 = self._prev2
self._prev2 = self._prev1
self._prev1 = val
return
dtema1 = self._prev1 - self._prev2
dtema2 = self._prev2 - self._prev3
turned_up = dtema2 < 0 and dtema1 > 0
turned_down = dtema2 > 0 and dtema1 < 0
if turned_up and self.Position <= 0:
self.BuyMarket()
elif turned_down and self.Position >= 0:
self.SellMarket()
self._prev3 = self._prev2
self._prev2 = self._prev1
self._prev1 = val
def CreateClone(self):
return exp_tema_strategy()