在 GitHub 上查看

大阳线声音策略

概述

大阳线声音策略 复刻了 MetaTrader 专家顾问 “BigBarSound” 的核心功能。策略会监控指定周期的已完成K线,当K线的波动幅度达到设定阈值时,就会给出提示。由于 StockSharp 是跨平台环境,策略不会直接播放音频文件,而是写入详细的日志信息,方便将其转发到任何受支持的通知系统。

本策略仅用于提示,不会提交委托或管理持仓,可作为更大交易流程中的辅助告警模块。

工作原理

  1. 策略按照 K线类型 参数订阅相应的K线序列。
  2. 对于每根收盘的K线,根据所选的 差值模式 计算K线大小:
    • OpenClose – 取收盘价与开盘价的绝对差。
    • HighLow – 取最高价与最低价之间的绝对差。
  3. 将得到的数值与 点数阈值 乘以标的物的 PriceStep 比较。当K线范围大于或等于阈值时,策略会记录一条模拟播放声音的日志。
  4. 如果启用了 显示提醒,则会额外写入一条醒目的提示日志,用于模拟 MQL 版本中的 Alert 弹窗。

策略只处理收盘后的K线,因此每根K线最多触发一次,与原始MQL脚本的一次性触发逻辑一致。

参数说明

  • Point Threshold (BarPoint) – 触发提示所需的最小点数,单位为价格跳动数。默认值 200 与原始脚本一致,并提供 50–500(步长50)的优化区间。
  • Difference Mode (DifferenceMode) – 指定如何衡量K线大小,可选开收盘差值或最高最低范围。
  • Sound File (SoundFile) – 需要播放的 WAV 文件名称。策略只会在日志中引用该名称,以模拟 MetaTrader 的 PlaySound 调用。
  • Show Alert (ShowAlert) – 若为 true,除了常规日志外还会写入额外的提醒信息。
  • Candle Type (CandleType) – 需要订阅的K线类型(时间框架),默认使用1分钟K线。

日志与提醒

策略通过 LogInfo 记录“播放声音”信息,并使用 AddInfoLog 输出额外提醒。日志中包含交易品种、K线时间以及实际测得的K线大小,便于在 StockSharp 的日志查看器或其他通知通道中使用。

若经纪商未提供有效的 PriceStep,策略会使用 1 作为后备值。此时应根据实际最小跳动幅度调整 Point Threshold 以保持正确的触发条件。

使用建议

  • 将策略附加到任何能够提供K线数据的标的,外汇、期货、股票或加密资产均可。
  • 可以订阅策略的日志输出,或继承类并在触发时执行自定义操作,从而与其他交易系统协同工作。
  • 策略不生成订单,因此 Volume 等仓位参数不会起作用。
  • 如需播放真实的提示音,可将 StockSharp 日志连接到声音通知器,或自行扩展代码调用操作系统的音频接口。

与原始MQL脚本的差异

  • 原脚本基于逐笔数据并手动检测新K线;本移植版本直接处理已收盘的K线,无需额外的触发标志即可保证每根K线只提示一次。
  • 播放声音的逻辑被日志消息取代,使策略能够在不同操作系统上保持一致的行为。
  • 参数名称遵循 StockSharp 的惯例,但其含义与原脚本完全一致:点数阈值、测量方式、可选提醒以及声音文件名。

依赖

策略不依赖额外指标,只需确保所选的 CandleType 能够从连接的数据源中获得对应的K线。

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>
/// Strategy that trades when a candle exceeds a configurable size threshold.
/// Based on the BigBarSound MetaTrader EA concept - trades in the direction of
/// large candles with ATR-based stop-loss and take-profit.
/// </summary>
public class BigBarSoundStrategy : Strategy
{
	/// <summary>
	/// Defines how the candle size is calculated.
	/// </summary>
	public enum BigBarDifferenceModes
	{
		/// <summary>
		/// Measure the difference between close and open prices.
		/// </summary>
		OpenClose,

		/// <summary>
		/// Measure the distance between the high and low of the candle.
		/// </summary>
		HighLow,
	}

	private readonly StrategyParam<int> _barPoint;
	private readonly StrategyParam<BigBarDifferenceModes> _differenceMode;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrStopMultiplier;
	private readonly StrategyParam<decimal> _atrTpMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private int _direction;

	/// <summary>
	/// Number of price steps required to trigger the alert.
	/// </summary>
	public int BarPoint
	{
		get => _barPoint.Value;
		set => _barPoint.Value = value;
	}

	/// <summary>
	/// Defines how the candle size is calculated.
	/// </summary>
	public BigBarDifferenceModes DifferenceMode
	{
		get => _differenceMode.Value;
		set => _differenceMode.Value = value;
	}

	/// <summary>
	/// ATR period for stop/take-profit calculations.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier for stop-loss distance.
	/// </summary>
	public decimal AtrStopMultiplier
	{
		get => _atrStopMultiplier.Value;
		set => _atrStopMultiplier.Value = value;
	}

	/// <summary>
	/// ATR multiplier for take-profit distance.
	/// </summary>
	public decimal AtrTpMultiplier
	{
		get => _atrTpMultiplier.Value;
		set => _atrTpMultiplier.Value = value;
	}

	/// <summary>
	/// Candle type used to monitor the market.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="BigBarSoundStrategy"/> class.
	/// </summary>
	public BigBarSoundStrategy()
	{
		_barPoint = Param(nameof(BarPoint), 180)
			.SetGreaterThanZero()
			.SetDisplay("Point Threshold", "Number of price steps required to trigger entry", "General")
			.SetOptimize(50, 500, 50);

		_differenceMode = Param(nameof(DifferenceMode), BigBarDifferenceModes.OpenClose)
			.SetDisplay("Difference Mode", "How the candle size is calculated", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
			.SetOptimize(7, 28, 7);

		_atrStopMultiplier = Param(nameof(AtrStopMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Stop Mult", "ATR multiplier for stop-loss", "Risk")
			.SetOptimize(1m, 3m, 0.5m);

		_atrTpMultiplier = Param(nameof(AtrTpMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("ATR TP Mult", "ATR multiplier for take-profit", "Risk")
			.SetOptimize(1m, 4m, 0.5m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to monitor", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_direction = 0;
	}

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

		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

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

		// Manage existing position
		if (Position > 0 && _direction > 0)
		{
			if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
			{
				SellMarket(Position);
				_direction = 0;
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
			}
		}
		else if (Position < 0 && _direction < 0)
		{
			if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
			{
				BuyMarket(Math.Abs(Position));
				_direction = 0;
				_stopPrice = 0m;
				_takeProfitPrice = 0m;
			}
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0)
			return;

		if (atrValue <= 0m)
			return;

		// Calculate candle size
		var difference = DifferenceMode == BigBarDifferenceModes.OpenClose
			? Math.Abs(candle.ClosePrice - candle.OpenPrice)
			: candle.HighPrice - candle.LowPrice;

		var priceStep = Security?.PriceStep;
		var step = priceStep is null or <= 0m ? 1m : priceStep.Value;
		var threshold = step * BarPoint;

		if (difference < threshold)
			return;

		var isBullish = candle.ClosePrice > candle.OpenPrice;
		var stopDist = atrValue * AtrStopMultiplier;
		var tpDist = atrValue * AtrTpMultiplier;

		if (isBullish)
		{
			BuyMarket(Volume);
			_direction = 1;
			_stopPrice = candle.ClosePrice - stopDist;
			_takeProfitPrice = candle.ClosePrice + tpDist;
		}
		else
		{
			SellMarket(Volume);
			_direction = -1;
			_stopPrice = candle.ClosePrice + stopDist;
			_takeProfitPrice = candle.ClosePrice - tpDist;
		}
	}
}