大阳线声音策略
概述
大阳线声音策略 复刻了 MetaTrader 专家顾问 “BigBarSound” 的核心功能。策略会监控指定周期的已完成K线,当K线的波动幅度达到设定阈值时,就会给出提示。由于 StockSharp 是跨平台环境,策略不会直接播放音频文件,而是写入详细的日志信息,方便将其转发到任何受支持的通知系统。
本策略仅用于提示,不会提交委托或管理持仓,可作为更大交易流程中的辅助告警模块。
工作原理
- 策略按照 K线类型 参数订阅相应的K线序列。
- 对于每根收盘的K线,根据所选的 差值模式 计算K线大小:
- OpenClose – 取收盘价与开盘价的绝对差。
- HighLow – 取最高价与最低价之间的绝对差。
- 将得到的数值与 点数阈值 乘以标的物的
PriceStep比较。当K线范围大于或等于阈值时,策略会记录一条模拟播放声音的日志。 - 如果启用了 显示提醒,则会额外写入一条醒目的提示日志,用于模拟 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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
OPEN_CLOSE = 0
HIGH_LOW = 1
class big_bar_sound_strategy(Strategy):
def __init__(self):
super(big_bar_sound_strategy, self).__init__()
self._bar_point = self.Param("BarPoint", 180)
self._difference_mode = self.Param("DifferenceMode", OPEN_CLOSE)
self._atr_period = self.Param("AtrPeriod", 14)
self._atr_stop_multiplier = self.Param("AtrStopMultiplier", 2.0)
self._atr_tp_multiplier = self.Param("AtrTpMultiplier", 3.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(big_bar_sound_strategy, self).OnReseted()
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
def OnStarted2(self, time):
super(big_bar_sound_strategy, self).OnStarted2(time)
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
atr = AverageTrueRange()
atr.Length = int(self._atr_period.Value)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self.OnProcess).Start()
def OnProcess(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
pos = float(self.Position)
if pos > 0 and self._direction > 0:
if float(candle.LowPrice) <= self._stop_price or float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(pos)
self._direction = 0
self._stop_price = 0.0
self._take_profit_price = 0.0
elif pos < 0 and self._direction < 0:
if float(candle.HighPrice) >= self._stop_price or float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(abs(pos))
self._direction = 0
self._stop_price = 0.0
self._take_profit_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position != 0:
return
atr_v = float(atr_value)
if atr_v <= 0:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if int(self._difference_mode.Value) == OPEN_CLOSE:
difference = abs(close - open_p)
else:
difference = high - low
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and sec.PriceStep > 0 else 1.0
threshold = step * float(self._bar_point.Value)
if difference < threshold:
return
is_bullish = close > open_p
stop_dist = atr_v * float(self._atr_stop_multiplier.Value)
tp_dist = atr_v * float(self._atr_tp_multiplier.Value)
vol = float(self.Volume)
if is_bullish:
self.BuyMarket(vol)
self._direction = 1
self._stop_price = close - stop_dist
self._take_profit_price = close + tp_dist
else:
self.SellMarket(vol)
self._direction = -1
self._stop_price = close + stop_dist
self._take_profit_price = close - tp_dist
def CreateClone(self):
return big_bar_sound_strategy()