在 GitHub 上查看

定时买入策略

概述

定时买入策略 复刻了 MetaTrader 专家顾问 buy_order.mq4 的行为:通过 1 秒计时器连续发送市价买单。StockSharp 版本完全保留了这种节奏——它等待计时器与当前分钟内的预期秒数对齐后才提交下一笔订单,并在达到指定的订单数量后自动停止。

该实现依赖 StockSharp 的高级 Timer 服务,而不是手写循环,不需要任何指标或 K 线订阅,因此逻辑完全由时间驱动,结果可预测。

核心逻辑

  1. 启动策略时调用 StartProtection() 激活保护模块,并按照参数启动计时器(默认 1 秒)。
  2. 每次计时器回调都会检查策略是否在线、是否允许交易,并确认当前交易所时间的秒值是否与预期序列一致。
  3. 条件满足时,策略以设定的数量发送一笔市价买单。
  4. 重复上述流程,直到达到目标订单数量,然后策略自动停止。

通过在每笔交易前比较秒数,该策略严格复刻了原始 MQL 程序的“60 秒内 60 笔”节奏:第一单等到秒数归零才发出,之后每笔都对应下一秒。

参数

名称 类型 默认值 说明
OrderVolume decimal 0.01 每笔市价买单的交易量。若为非正数,策略会记录警告并停止。
OrdersToPlace int 60 需要发送的订单总数,达到后策略结束。
Interval TimeSpan 1s 计时器触发的时间间隔。保持 1 秒可最贴近原始 MQL 行为,也可以根据需求调整。

所有参数都通过 StrategyParam<T> 暴露,可在图形界面或优化器中修改和优化。

执行流程

  • 初始化OnReseted() 重置计数器,确保重新启动或优化时状态干净。
  • 启动OnStarted() 中重新设置计数器、启动计时器并启用保护。
  • 计时器回调OnTimer() 进行秒数同步、下单和日志记录,最终在完成最后一单后触发停止。
  • 结束CompleteStrategy() 防止重复停止,只调用一次 Stop()

转换说明

  • MQL 的 EventSetTimer(1) 映射为 Timer.Start(TimeSpan.FromSeconds(1), OnTimer)
  • MetaTrader 中的订单备注与魔术号在 StockSharp 中没有直接对应,因此改为使用日志跟踪进度。
  • 通过比较当前秒数而非计时器触发次数,维持每分钟 60 单的节奏。

使用建议

  1. 启动前设置好交易工具与投资组合。
  2. 根据品种合约大小和经纪商规则调整 OrderVolume
  3. 若需要更少的订单,降低 OrdersToPlace;若希望关闭按秒节奏,可自行修改代码移除秒数校验(进阶调整)。
  4. 关注日志输出,确认计时节奏与实际成交一致。

限制

  • 策略仅执行买入,没有卖出或止盈止损逻辑,后续仓位需手动或由外部风控处理。
  • 实际计时精度取决于操作系统和连接的计时器服务,若延迟过大可能导致节奏漂移。

文件结构

  • CS/TimedBuyOrderStrategy.cs —— C# 主体实现。
  • README.md —— 英文文档。
  • README_ru.md —— 俄文文档。

根据项目要求,本策略暂不提供 Python 版本,如需可后续自行补充。

namespace StockSharp.Samples.Strategies;

using System;

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

/// <summary>
/// Strategy that submits a sequence of market buy orders on each candle close.
/// After a configurable number of orders have been placed, it stops.
/// </summary>
public class TimedBuyOrderStrategy : Strategy
{
	private readonly StrategyParam<int> _ordersToPlace;
	private readonly StrategyParam<DataType> _candleType;

	private int _ordersPlaced;

	/// <summary>
	/// Initializes a new instance of the <see cref="TimedBuyOrderStrategy"/> class.
	/// </summary>
	public TimedBuyOrderStrategy()
	{
		_ordersToPlace = Param(nameof(OrdersToPlace), 60)
			.SetGreaterThanZero()
			.SetDisplay("Orders To Place", "Number of sequential buy orders before stopping", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
	}

	/// <summary>
	/// Total number of buy orders to submit before the strategy stops.
	/// </summary>
	public int OrdersToPlace
	{
		get => _ordersToPlace.Value;
		set => _ordersToPlace.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_ordersPlaced = 0;
	}

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

		_ordersPlaced = 0;

		var sma = new SMA { Length = 5 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, OnProcess)
			.Start();
	}

	private void OnProcess(ICandleMessage candle, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_ordersPlaced >= OrdersToPlace)
			return;

		BuyMarket();

		_ordersPlaced++;
	}
}