在 GitHub 上查看

iTrade 策略

iTrade 策略是从 MetaTrader 专家顾问 iTrade 转换而来,用于手动管理做空组合。它完全复刻原始 EA 的图表按钮流程:当用户发出卖出请求时,策略会按照马丁格尔规则开仓,并持续监控所有空头的浮动盈亏,在达到指定阈值后同时平掉浮动利润最高和最低的仓位。

核心逻辑

  • 仅在用户显式调用 QueueSellRequest() 时才会下达卖出市价单。
  • 第一笔订单使用 Initial Volume 指定的手数;每出现一次亏损,下一笔订单的手数都会乘以 Martingale Multiplier,盈利则将序列重置为基础手数。
  • 使用当前最优卖价计算浮动盈亏。当每笔持仓的平均浮盈达到 Average Profit Target 时,在最多 Base Trade Count 笔交易的范围内,平掉最赚钱和最亏损的两笔仓位。
  • 当持仓数量超过 Base Trade Count 时,只有达到 Extended Profit Target 才会触发上述平仓流程。
  • 盈亏计算依赖交易品种的 PriceStepStepPrice,若缺少这两个属性,策略会在启动时抛出异常。

参数

名称 说明
InitialVolume 第一笔马丁格尔订单的基础手数。
MartingaleMultiplier 每次亏损后用于放大手数的倍数。
AverageProfitTarget 在初始批次内触发平仓的平均浮盈阈值(账户货币)。
ExtendedAverageProfitTarget 当持仓数超出基础批次时使用的平均浮盈阈值。
BaseTradeCount 视为“初始批次”的最大持仓数量。
ControlInterval 内部计时器的执行间隔。

使用说明

  1. 启动前设置好 SecurityPortfolio 以及所需参数。
  2. 调用 QueueSellRequest() 即可模拟原 EA 的按钮,策略会自动计算手数并发送市价卖单。
  3. 策略会保存最近 200 次平仓结果,以复现原始马丁格尔手数计算方式。
  4. 平仓通过提交与目标仓位等量的买入市价单实现。

与 MetaTrader 版本的差异

  • 原策略通过图表按钮触发,这里改为调用 QueueSellRequest()
  • 下单与成交由 StockSharp 的市场单完成,支持自动汇总部分成交。
  • 盈亏阈值按照 StepPrice 计算货币金额,而不是使用 MetaTrader 的票据利润函数。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class ITradeStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ITradeStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}