在 GitHub 上查看

Rnd Trade 策略

概述

  • 将 MetaTrader 5 专家顾问 RndTrade.mq5 转换为 StockSharp 的高级策略 API。
  • 每隔固定时间关闭现有持仓,并立即以随机方向开立新的市价单。
  • 通过订阅与间隔相同的时间周期蜡烛,来替代原始 MQL 中的定时器回调。

参数

名称 类型 默认值 说明
IntervalMinutes int 60 从平掉当前仓位到开立下一笔随机仓位之间的分钟数,必须大于零。
Volume decimal 1 市价单使用的交易量,来自基类 Strategy

数据订阅

  • 订阅与 IntervalMinutes 相同周期的时间框蜡烛(例如 60 → 60 分钟蜡烛)。
  • 仅在蜡烛收盘 (CandleStates.Finished) 时执行逻辑,保证每个间隔只触发一次。

交易流程

  1. 等待每根间隔蜡烛收盘。
  2. 在策略尚未形成、离线或禁止交易时跳过处理。
  3. 平掉上一个间隔留下的仓位。
  4. 生成随机数,决定做多还是做空。
  5. 以设定的交易量提交市价买单或卖单。

实现细节

  • 使用 SubscribeCandles().Bind(ProcessCandle) 管线,无需手动处理指标值或集合。
  • 在启动阶段调用 StartProtection(),激活内置的风控模块(默认无止损/止盈设置)。
  • Random 随机数生成器复现了原始 MQL 中 MathRand() 的行为。
  • 代码中提供英文注释,说明每一步转换如何映射到 StockSharp 功能。

与原版 MQL 策略的差异

  • 通过时间周期蜡烛模拟 OnTimer 事件,不再使用 MetaTrader 的定时器 API。
  • 平仓使用 ClosePosition(),无需遍历仓位列表并逐一调用 PositionClose
  • 仓位规模依赖 Volume 属性,而不是查询品种的最小手数。
  • 滑点和成交方式由连接的券商或仿真器控制,策略内部不再单独设置。

使用方法

  1. 在 StockSharp 中将策略连接到目标投资组合和交易品种。
  2. 根据需求设置 IntervalMinutesVolume
  3. 启动策略,系统会在每个时间间隔自动平仓并重新开仓。
  4. 当前仅提供 C# 版本,暂无 Python 实现。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Random direction trading strategy converted from the MetaTrader RndTrade EA.
/// The strategy closes any open position on each interval and immediately opens a new random position.
/// </summary>
public class RndTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _intervalMinutes;

	/// <summary>
	/// Interval in minutes between closing the current position and opening a new random one.
	/// </summary>
	public int IntervalMinutes
	{
		get => _intervalMinutes.Value;
		set => _intervalMinutes.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="RndTradeStrategy"/> class.
	/// </summary>
	public RndTradeStrategy()
	{
		_intervalMinutes = Param(nameof(IntervalMinutes), 360)
			.SetGreaterThanZero()
			.SetDisplay("Interval Minutes", "Minutes between closing and opening positions", "General");

		Volume = 1;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, TimeSpan.FromMinutes(IntervalMinutes).TimeFrame())];
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Use time-based candles as a deterministic timer replacement.
		var timeFrame = TimeSpan.FromMinutes(IntervalMinutes).TimeFrame();

		var subscription = SubscribeCandles(timeFrame);
		subscription
			.Bind(ProcessCandle)
			.Start();

	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Process only final candles to execute logic exactly once per interval.
		if (candle.State != CandleStates.Finished)
			return;

		// Always close the existing position before selecting a new random direction.
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));

		// Derive a deterministic pseudo-random direction from the candle data.
		if (ShouldBuy(candle))
		{
			// Enter long after flattening the previous position.
			if (Position <= 0)
				BuyMarket(Volume);
		}
		else
		{
			// Enter short after flattening the previous position.
			if (Position >= 0)
				SellMarket(Volume);
		}
	}

	private static bool ShouldBuy(ICandleMessage candle)
	{
		var hash = HashCode.Combine(candle.OpenTime.Ticks, candle.ClosePrice, candle.TotalVolume);
		return (hash & 1) == 0;
	}
}