在 GitHub 上查看

Expotest 策略

概述

Expotest 策略是对原始 Expotest.mq5 专家顾问的 StockSharp 版本转换。策略仅交易单一标的,依靠抛物线 SAR 指标判定方向,并采用类似马丁格尔的资金管理规则。任意时刻只保持一笔仓位,并通过预设的止损与止盈距离来离场。

交易逻辑

  • 指标:在所选蜡烛周期上计算抛物线 SAR。加速因子 (SarStep) 与最大加速因子 (SarMaximum) 都可以配置。
  • 入场条件:仅在没有持仓时评估最新收盘蜡烛。
    • 当 SAR 值小于或等于收盘价时,开多仓。
    • 当 SAR 值大于或等于收盘价时,开空仓。
  • 出场条件:止损与止盈按入场价的固定价格步长计算。每根新蜡烛都会检查最高价/最低价是否触及这两个价格,一旦触发即平仓,并记录本次交易是盈利还是亏损,以便下一次调整仓位大小。

仓位管理

  • 基础手数:当 FixedVolume 大于零时直接使用该固定手数。否则根据当前账户权益、RiskPercentStopLossPoints 估算仓位;若无法计算则退回到默认的 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;
	}
}