在 GitHub 上查看

随机方向策略

概述

该策略是 MetaTrader 5 专家顾问“At random”(MQL ID 39835)的 StockSharp 移植版本。原始机器人展示了在必须持续持仓的情况下,完全随机的决策流程会如何表现。每根完成的 K 线都会触发一次“抛硬币”,决定下一步是买入还是卖出。StockSharp 版本保留了这一思想,并使用高级 API 原语(SubscribeCandlesBuyMarketSellMarket)实现,从而可以方便地在 Designer 或 Runner 中运行。

移植实现刻意不包含止盈、止损或移动止损,与参考的 MQL 脚本保持一致。因此它更适合作为测试用的工具或教学示例,而不是盈利策略。

交易逻辑

  1. 订阅配置好的蜡烛序列(CandleType)。默认周期为 15 分钟,用以模拟 MetaTrader 的“当前时间框架”。
  2. 当一根蜡烛完成时,检查是否需要关闭旧持仓。启用 CloseBeforeReversal 时,策略会先平仓并等待持仓归零后才会发出下一笔订单。
  3. 使用伪随机数生成器产生一个交易方向。可选参数 RandomSeed 允许通过固定种子生成可复现的序列,便于回测对比。
  4. 按固定的 TradeVolume 下达市价单。多空对称,且没有任何保护性订单。可以通过 LogSignals 开启日志,以便追踪每次随机决策。

由于每根蜡烛只会触发一次随机决策,策略在任何时刻要么空仓,要么只持有一笔仓位。仓位只会在下一根 K 线出现时被平仓或反向。

订单管理与风险

  • 所有开仓和平仓都通过 BuyMarket / SellMarket 完成,没有限价或止损订单。
  • 如果关闭 CloseBeforeReversal,策略可能会连续持仓:新的随机信号可以直接开出相反方向,而无需显式平掉上一笔交易。
  • 策略没有资金管理或账户保护逻辑。移植的目的在于还原原始专家顾问的行为,便于教学和基础设施测试。

参数

参数 说明
TradeVolume 每次随机下单使用的基础数量,必须为正数。
CloseBeforeReversal 是否在执行下一笔随机交易前强制平掉当前持仓。
LogSignals 每次生成随机方向时写入 AddInfoLog 日志。
CandleType 决定随机抛硬币触发频率的蜡烛时间框架。
RandomSeed 伪随机数生成器的种子,设置为 0 表示使用系统时间。

使用提示

  • 与原始 MQL 程序一样,移植版本没有止盈止损。如需实盘实验,必须自行添加风险控制。
  • 固定随机种子可以生成可复现的数据集,便于优化或对比不同的随机序列。
  • 建议在测试阶段启用日志,因为纯随机策略在图表上的反馈非常有限。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that randomly opens long or short positions based on a random threshold.
/// Mirrors the "At random" MetaTrader expert.
/// </summary>
public class AtRandomStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _randomSeed;

	private Random _random;
	private int _barCount;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RandomSeed
	{
		get => _randomSeed.Value;
		set => _randomSeed.Value = value;
	}

	public AtRandomStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe that triggers random decisions", "Data");

		_randomSeed = Param(nameof(RandomSeed), 42)
			.SetDisplay("Random Seed", "Seed for the pseudo random generator (0 = system clock)", "Diagnostics");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_random = RandomSeed == 0 ? new Random() : new Random(RandomSeed);
		_barCount = 0;

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_barCount++;

		// Only trade occasionally to keep turnover within runner limits.
		if (_random.Next(0, 6) != 0)
			return;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Random direction
		var goLong = _random.Next(0, 2) == 0;

		if (goLong)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_random = null;
		_barCount = 0;

		base.OnReseted();
	}
}