首页
/
策略示例
在 GitHub 上查看
点差统计采集策略
概述
Spread Data Collector 策略 是 MetaTrader 5 工具“Spread data collector”(MQL 编号 33314)的 StockSharp 版本。原始 EA 不会下单,只是监听买/卖报价并统计点差落在各个范围内的次数;当交易年度发生变化或 EA 被停止时,它会打印汇总结果。本移植使用高级的 SubscribeLevel1() API,实现完全相同的逻辑,并允许用户修改每个点差区间的上限。
运行流程
启动时,策略会订阅主交易品种 Security 的一级行情(买/卖价)。
只要同时得到买价和卖价,就会计算点差,并通过 Security.PriceStep 将“点”转换为实际价格差。
策略维护六个计数器:
点差严格小于第一条上限。
点差位于第一与第二条上限之间。
点差位于第二与第三条上限之间。
点差位于第三与第四条上限之间。
点差位于第四与第五条上限之间。
点差大于或等于第五条上限。
交易年度来自 Level1ChangeMessage.ServerTime 的交易所时间。当年份发生变化时,策略会输出上一年的统计并清零计数。
停止策略时,会先输出当年的统计结果,再结束运行。
因此,该策略与原工具一样完全“被动”,适合长期监控流动性与点差质量,不会发送任何订单。
参数
所有参数以 点 (points) 表示,实际价格差通过公式 points × Security.PriceStep 计算。
参数
默认值
说明
FirstBucketPoints
10
第一个区间的上限,点差严格小于该值时计入此组。
SecondBucketPoints
20
第二个区间的上限,点差落在 [FirstBucketPoints, SecondBucketPoints) 内计入此组。
ThirdBucketPoints
30
第三个区间的上限,点差落在 [SecondBucketPoints, ThirdBucketPoints) 内计入此组。
FourthBucketPoints
40
第四个区间的上限,点差落在 [ThirdBucketPoints, FourthBucketPoints) 内计入此组。
FifthBucketPoints
50
第五个区间的上限,点差落在 [FourthBucketPoints, FifthBucketPoints) 内计入此组。
所有阈值必须严格递增。如果 Security.PriceStep 未设置或小于等于 0,策略在启动时会抛出异常,以避免产生错误的统计数据。
日志输出
策略通过 AddInfoLog 打印统计信息,格式如下:
Year=2024 Spread<=10pts=15342 Spread_10_20pts=2841 Spread_20_30pts=912 ... Spread>50pts=37
该格式与 MetaTrader 中的 Print 输出相同,方便在不同平台之间对比。可以使用 StockSharp 日志查看器或将日志重定向到文件进行分析。
使用步骤
在 Strategy.Security 中指定品种,并确保 PriceStep 与 MetaTrader 中“点”的定义一致(多数外汇品种为 0.0001)。
如需不同的点差区间,可调整各个上限,务必保持严格递增。
启动策略即可,无需担心订单会被发送。
定期查看年度日志,了解点差在不同时间段的表现。
该策略资源占用极低,可以与实盘交易系统并行运行,用于构建长期的点差分布数据、验证流动性假设以及监控券商条件。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Spread Data Collector strategy: RSI mean reversion.
/// Buys when RSI crosses below oversold, sells when RSI crosses above overbought.
/// </summary>
public class SpreadDataCollectorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private decimal _prevRsi;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public SpreadDataCollectorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_oversold = Param(nameof(Oversold), 30m)
.SetDisplay("Oversold", "RSI oversold level", "Levels");
_overbought = Param(nameof(Overbought), 70m)
.SetDisplay("Overbought", "RSI overbought level", "Levels");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevRsi >= Oversold && rsiValue < Oversold && Position <= 0)
BuyMarket();
else if (_prevRsi <= Overbought && rsiValue > Overbought && Position >= 0)
SellMarket();
}
_prevRsi = rsiValue;
_hasPrev = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class spread_data_collector_strategy(Strategy):
def __init__(self):
super(spread_data_collector_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._oversold = self.Param("Oversold", 30.0)
self._overbought = self.Param("Overbought", 70.0)
self._prev_rsi = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.Value = value
def OnReseted(self):
super(spread_data_collector_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(spread_data_collector_strategy, self).OnStarted2(time)
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
if self._has_prev:
if self._prev_rsi >= self.Oversold and rsi_val < self.Oversold and self.Position <= 0:
self.BuyMarket()
elif self._prev_rsi <= self.Overbought and rsi_val > self.Overbought and self.Position >= 0:
self.SellMarket()
self._prev_rsi = rsi_val
self._has_prev = True
def CreateClone(self):
return spread_data_collector_strategy()