在 GitHub 上查看
Symbol Swap 策略
Symbol Swap Strategy 是 MetaTrader 5 工具 “Symbol Swap” 的 StockSharp 版本。原始的 MQL5 面板允许交易者输入任意品种,立即把图表切换到该符号,并显示一个包含时间、OHLC 价格、tick 成交量与点差的数据窗口。本次移植保持同样的职责,同时完全依赖 StockSharp 的高级订阅接口实现。
工作流程
- 启动后策略会解析需要监控的标的。优先使用
WatchedSecurityId,如果为空则回退到启动器中配置的 Strategy.Security。
- 通过
SubscribeCandles(...) 订阅所选 CandleType 的蜡烛。每当蜡烛收盘时都会提供 Open/High/Low/Close 和 tick 成交量,用来更新面板字段。
- 使用
SubscribeLevel1(...) 订阅最优买卖报价。每次报价变化都会重新计算点差,与 MetaTrader 的数据窗口保持一致。
- 生成的文本块可以写入策略日志(
OutputMode = Log),也可以借助 DrawText(...) 叠加在图表上(OutputMode = Chart),从而复刻 MQL 浮动面板的视觉效果。
- 运行过程中调用
SwapSecurity("TICKER") 会通过 SecurityProvider.LookupById 查找新的品种,并无缝重建蜡烛与 Level 1 订阅。
策略仅用于信息展示,不会提交任何订单。它既可作为行情面板单独使用,也可与其他交易系统同时运行。
参数
| 名称 |
说明 |
默认值 |
CandleType |
构建 OHLC 与 tick 成交量所用的蜡烛类型。 |
TimeFrame(1 minute) |
WatchedSecurityId |
可选的标的代码。留空时使用 Strategy.Security。 |
空 |
OutputMode |
信息块的呈现方式:Chart 表示绘制在图表上,Log 表示写入日志。 |
Chart |
公共方法
| 方法 |
说明 |
SwapSecurity(string securityId) |
通过当前 SecurityProvider 查找给定代码,并立即将面板切换到该标的。每次调用都会先清理旧的订阅,再创建新的蜡烛和 Level 1 订阅。 |
使用提示
- 请确认连接器能够识别所需的代码,否则
SecurityProvider.LookupById 会抛出异常。
- 当
OutputMode = Chart 时,策略会自动创建图表区域、绘制对应的蜡烛,并叠加数据文本。若选择 Log,则只输出文本日志。
- Tick 成交量直接取自蜡烛的
TotalVolume 字段,与 MetaTrader 的处理方式相同。
- 只有同时获得 best bid 和 best ask 时才会显示点差,否则字段显示
n/a。
移植说明
- MQL 中的定时器循环被 StockSharp 的订阅机制取代:蜡烛在收盘时触发一次,Level 1 则在每次报价变动时触发。
- 九个标签被组合成一个多行文本,顺序保持与原程序一致:Time、Period、Symbol、Close、Open、High、Low、Tick Volume、Spread。
- 不再需要手动把符号加入 Market Watch,策略直接通过
SecurityProvider 查找标的。
- 整个实现只使用高级 API(
SubscribeCandles、SubscribeLevel1、DrawText、AddInfo),符合仓库的编码要求。
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>
/// Market monitoring strategy that tracks price metrics and trades on significant
/// spread changes. Simplified from the MetaTrader "Symbol Swap" display panel.
/// </summary>
public class SymbolSwapStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<decimal> _spreadThreshold;
private SimpleMovingAverage _sma;
private decimal _entryPrice;
/// <summary>
/// Candle type for monitoring.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// SMA period for trend detection.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <summary>
/// Price deviation threshold for entry signals.
/// </summary>
public decimal SpreadThreshold
{
get => _spreadThreshold.Value;
set => _spreadThreshold.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public SymbolSwapStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series for signals", "General");
_smaPeriod = Param(nameof(SmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "Moving average period", "Indicators");
_spreadThreshold = Param(nameof(SpreadThreshold), 3m)
.SetGreaterThanZero()
.SetDisplay("Spread Threshold", "Price deviation from SMA to trigger entry", "Signals");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = SmaPeriod };
SubscribeCandles(CandleType)
.Bind(_sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var price = candle.ClosePrice;
// Exit on mean reversion
if (Position != 0 && _entryPrice > 0m)
{
var pnl = Position > 0
? price - _entryPrice
: _entryPrice - price;
// Exit on profit or loss threshold
if (pnl >= SpreadThreshold || pnl <= -SpreadThreshold * 2m)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_entryPrice = 0m;
return;
}
}
// Entry on deviation
if (Position == 0)
{
var deviation = price - smaValue;
if (deviation > SpreadThreshold)
{
SellMarket();
_entryPrice = price;
}
else if (deviation < -SpreadThreshold)
{
BuyMarket();
_entryPrice = price;
}
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class symbol_swap_strategy(Strategy):
def __init__(self):
super(symbol_swap_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle series for signals", "General")
self._sma_period = self.Param("SmaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("SMA Period", "Moving average period", "Indicators")
self._spread_threshold = self.Param("SpreadThreshold", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Spread Threshold", "Price deviation from SMA to trigger entry", "Signals")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SmaPeriod(self):
return self._sma_period.Value
@SmaPeriod.setter
def SmaPeriod(self, value):
self._sma_period.Value = value
@property
def SpreadThreshold(self):
return self._spread_threshold.Value
@SpreadThreshold.setter
def SpreadThreshold(self, value):
self._spread_threshold.Value = value
def OnReseted(self):
super(symbol_swap_strategy, self).OnReseted()
self._entry_price = 0.0
def OnStarted2(self, time):
super(symbol_swap_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self.SmaPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(sma, self._process_candle) \
.Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
price = float(candle.ClosePrice)
sma_v = float(sma_value)
threshold = float(self.SpreadThreshold)
# Exit on mean reversion
if self.Position != 0 and self._entry_price > 0:
pnl = price - self._entry_price if self.Position > 0 else self._entry_price - price
if pnl >= threshold or pnl <= -threshold * 2:
if self.Position > 0:
self.SellMarket(abs(self.Position))
else:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
return
# Entry on deviation
if self.Position == 0:
deviation = price - sma_v
if deviation > threshold:
self.SellMarket()
self._entry_price = price
elif deviation < -threshold:
self.BuyMarket()
self._entry_price = price
def CreateClone(self):
return symbol_swap_strategy()