在 GitHub 上查看

HistoryInfoEaStrategy

概述

HistoryInfoEaStrategy 将 MT4 的 “HistoryInfo” 工具移植到 StockSharp。策略不会在图表上绘制文本,而是监听 OnNewMyTrade 事件,对满足筛选条件的成交进行统计。聚合结果通过 LastSnapshot 属性提供,同时写入日志,方便界面或外部服务读取。

策略本身不会发送订单。它适用于与其他自动化策略或人工交易共同运行,只负责分析匹配条件的成交。

参数

参数 说明
FilterType 交易筛选模式:CountByUserOrderIdCountByCommentCountBySecurity
MagicNumber 期望的 Order.UserOrderId(仅在 CountByUserOrderId 模式下使用)。留空表示不匹配任何成交。
OrderComment 订单注释前缀(Order.Comment)。仅在 CountByComment 模式下使用。默认值 "OrdersComment" 延续 MT4 的占位符,通常需要替换。
SecurityId 证券标识(Security.Id)。仅在 CountBySecurity 模式下有效。默认值 "OrdersSymbol" 也是占位符。

聚合指标

每当找到一笔符合条件的成交,LastSnapshot 都会刷新,包含以下字段:

  • FirstTrade / LastTrade:最早和最新成交的时间戳。
  • TotalVolume:累积成交量,单位与交易工具相同(手、合约等)。
  • TotalProfitMyTrade.PnL 减去手续费后的已实现盈亏。
  • TotalPips:根据 Security.PriceStepSecurity.StepPrice 计算的点数收益,并考虑 MT4 常见的 5/3 位小数放大规则。
  • TradeCount:通过筛选的成交数量。

日志中会打印一行与 MT4 Comment() 输出类似的摘要,方便快速查看。

使用方法

  1. 将策略连接到与其它策略相同的投资组合和证券。
  2. 选择 FilterType,并填写相应参数(魔术号、注释前缀或证券标识)。
  3. 启动策略,满足条件的第一笔成交出现后,LastSnapshot 会立即提供统计结果,同时日志也会更新。
  4. 重启或手动重置策略时,所有计数会自动清零。

提示: 点数统计依赖正确的合约元数据。请确保 Security.PriceStepSecurity.StepPrice 已配置;若缺失其中任意一个,TotalPips 会保持为零,但盈亏金额仍然会累加。

移植注意事项

  • MT4 脚本在 OnTick 中循环遍历 OrdersHistoryTotal()。本策略改为响应式实现,只在收到新的 MyTrade 时更新数据,无需轮询。
  • MT4 通过 OrderProfit + OrderCommission + OrderSwap 计算收益。StockSharp 提供 MyTrade.PnLMyTrade.Commission 字段(多数交易所已在 PnL 中包含掉期)。为保持一致,策略会从 PnL 中减去手续费。
  • 为了贴近原作的默认体验,保留了 "OrdersComment""OrdersSymbol" 等占位符。实际使用前请替换成有效值。
  • 原本的图表文字输出被结构化数据和日志取代,界面层可以按需展示。
  • 策略不会创建新的订单,因此可以在“观察”模式下分析第三方策略的成交流。

扩展建议

  • 订阅 LastSnapshot,将统计数据推送到监控面板或告警系统。
  • 根据接入方提供的元数据,增加更多筛选条件(例如按投资组合或自定义标签)。
  • 定期导出快照到 CSV/JSON,以生成历史报表。
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 HistoryInfoEaStrategy : 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 HistoryInfoEaStrategy()
	{
		_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;
	}
}