在 GitHub 上查看
Expotest 策略
概述
Expotest 策略是对原始 Expotest.mq5 专家顾问的 StockSharp 版本转换。策略仅交易单一标的,依靠抛物线 SAR 指标判定方向,并采用类似马丁格尔的资金管理规则。任意时刻只保持一笔仓位,并通过预设的止损与止盈距离来离场。
交易逻辑
- 指标:在所选蜡烛周期上计算抛物线 SAR。加速因子 (
SarStep) 与最大加速因子 (SarMaximum) 都可以配置。
- 入场条件:仅在没有持仓时评估最新收盘蜡烛。
- 当 SAR 值小于或等于收盘价时,开多仓。
- 当 SAR 值大于或等于收盘价时,开空仓。
- 出场条件:止损与止盈按入场价的固定价格步长计算。每根新蜡烛都会检查最高价/最低价是否触及这两个价格,一旦触发即平仓,并记录本次交易是盈利还是亏损,以便下一次调整仓位大小。
仓位管理
- 基础手数:当
FixedVolume 大于零时直接使用该固定手数。否则根据当前账户权益、RiskPercent 与 StopLossPoints 估算仓位;若无法计算则退回到默认的 Strategy.Volume。
- 翻倍规则:若上一笔交易亏损,则下一笔交易的手数会在基础手数的基础上翻倍;若上一笔盈利,则恢复到基础手数。
可配置参数
CandleType – 用于生成蜡烛的行情类型或时间框架。
SarStep – 抛物线 SAR 的初始加速因子。
SarMaximum – 抛物线 SAR 的最大加速因子。
StopLossPoints – 止损距离,按价格步长计。
TakeProfitPoints – 止盈距离,按价格步长计。
RiskPercent – 动态仓位 sizing 时每笔交易愿意承担的账户权益百分比。
FixedVolume – 固定下单手数,设置为 0 时启用风险百分比计算。
其他说明
- 为了贴近原始 MQL 逐笔逻辑并兼容 StockSharp 的订阅机制,策略仅处理已收盘的蜡烛。
- 止损和止盈价格在策略内部跟踪,而不是提交额外的挂单,便于回测与调试。
- 根据要求,本策略暂不提供 Python 版本。
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Parabolic SAR based strategy converted from the Expotest MQL expert advisor.
/// Enters long when SAR is below price, short when SAR is above price.
/// </summary>
public class ExpotestStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMaximum;
private bool _prevSarBelow;
private bool _initialized;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
public decimal SarMaximum
{
get => _sarMaximum.Value;
set => _sarMaximum.Value = value;
}
public ExpotestStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type for signal generation", "General");
_sarStep = Param(nameof(SarStep), 0.02m)
.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Indicators");
_sarMaximum = Param(nameof(SarMaximum), 0.2m)
.SetDisplay("SAR Maximum", "Maximum acceleration factor for Parabolic SAR", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSarBelow = false;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSarBelow = false;
_initialized = false;
var sar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationMax = SarMaximum
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sar, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sar);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal sarValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sarBelow = sarValue < candle.ClosePrice;
if (!_initialized)
{
_prevSarBelow = sarBelow;
_initialized = true;
return;
}
// SAR flipped from above to below price => Buy signal
if (sarBelow && !_prevSarBelow && Position <= 0)
{
BuyMarket();
}
// SAR flipped from below to above price => Sell signal
else if (!sarBelow && _prevSarBelow && Position >= 0)
{
SellMarket();
}
_prevSarBelow = sarBelow;
}
}
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 ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class expotest_strategy(Strategy):
def __init__(self):
super(expotest_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle type for signal generation", "General")
self._sar_step = self.Param("SarStep", 0.02) \
.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Indicators")
self._sar_maximum = self.Param("SarMaximum", 0.2) \
.SetDisplay("SAR Maximum", "Maximum acceleration factor for Parabolic SAR", "Indicators")
self._prev_sar_below = False
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def SarStep(self):
return self._sar_step.Value
@property
def SarMaximum(self):
return self._sar_maximum.Value
def OnReseted(self):
super(expotest_strategy, self).OnReseted()
self._prev_sar_below = False
self._initialized = False
def OnStarted2(self, time):
super(expotest_strategy, self).OnStarted2(time)
self._prev_sar_below = False
self._initialized = False
sar = ParabolicSar()
sar.Acceleration = self.SarStep
sar.AccelerationMax = self.SarMaximum
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(sar, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def _on_process(self, candle, sar_value):
if candle.State != CandleStates.Finished:
return
sv = float(sar_value)
sar_below = sv < float(candle.ClosePrice)
if not self._initialized:
self._prev_sar_below = sar_below
self._initialized = True
return
if sar_below and not self._prev_sar_below and self.Position <= 0:
self.BuyMarket()
elif not sar_below and self._prev_sar_below and self.Position >= 0:
self.SellMarket()
self._prev_sar_below = sar_below
def CreateClone(self):
return expotest_strategy()