策略与StockSharp平台的兼容性
在StockSharp中开发交易策略时,重要的是要考虑它们与各种平台的兼容性:Designer、Shell、Runner 和 云回测。遵循以下建议,您将创建在所有环境中都能正确运行的策略。
策略构造器参数
避免在构造函数中使用参数
为了确保与 StockSharp 平台的兼容性,特别是在云回测时,你不应该在策略构造函数中添加参数:
// 正确:无参数构造函数
public class SmaStrategy : Strategy
{
public SmaStrategy()
{
// 参数初始化
}
}
// 错误:带参数的构造函数
public class SmaStrategy : Strategy
{
public SmaStrategy(int longLength, int shortLength) // Don't use this approach
{
// ...
}
}
StockSharp 平台使用无参数构造函数创建策略实例。如果你的策略需要带参数的构造函数,则不会被正确初始化。
使用 StrategyParam 而不是常规属性
StrategyParam 的好处
不要创建常规的 C# 属性然后覆盖 Save 和 Load 方法,而是对所有可自定义参数使用 StrategyParam<T>:
// 正确:使用 StrategyParam
private readonly StrategyParam<int> _longSmaLength;
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("长期 SMA 周期", string.Empty, "基本设置");
}
// 错误:使用普通属性
private int _longSmaLength = 80; // Don't use this approach
public int LongSmaLength
{
get => _longSmaLength;
set => _longSmaLength = value;
}
通过 StrategyParam<T> 创建的参数将自动:
- 在平台用户界面中显示
- 被保存和加载而不覆盖
Save和Load方法 - 用于优化
- 发送到云回测时应正确序列化
与用户界面合作
使用抽象而不是直接访问用户界面
不要直接访问用户界面元素,而是使用 StockSharp 提供的抽象。
// 正确方式:使用 IChart
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// 获取运行环境提供的图表
_chart = GetChart();
if (_chart != null)
{
// 图表可用(例如在 Designer 或 Shell 中)
InitChart();
}
else
{
// 图表不可用(例如在 Runner 或云端回测中)
// 策略在没有可视化时仍继续运行
}
}
private void InitChart()
{
// 通过抽象接口配置图表
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
// ...
}
Strategy.GetChart() 方法在当前运行时环境中,如果有图表可用,将返回 IChart 接口。如果策略在控制台 Runner 或云回测中运行,而没有图形界面,该方法将返回 null。
IChart 接口提供用于操作图表的方法:
- AddArea - 向图表添加一个区域
- RemoveArea - 移除一个区域
- AddElement - 向图表中添加一个元素
- RemoveElement - 移除一个元素
- 重置 - 重置元素值
检查图表可用性
在使用图表之前,请始终检查其可用性:
private void DrawCandlesAndIndicators(ICandleMessage candle, IIndicatorValue longSma, IIndicatorValue shortSma)
{
if (_chart == null) return; // 重要检查
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, longSma)
.Add(_shortSmaIndicatorElement, shortSma);
_chart.Draw(data);
}
线程与同步
避免创建额外的线程
在 StockSharp 中,你不需要为数据处理创建额外的线程。所有事件(市场数据、交易)都在单个线程中进行:
// 正确:使用标准事件处理器
private void ProcessCandle(ICandleMessage candle)
{
// 在主线程中处理 K线
var longSmaIsFormedPrev = _longSma.IsFormed;
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// 错误:创建额外线程
private void ProcessCandle(ICandleMessage candle)
{
// 不要这样做
Task.Run(() => {
var longSmaIsFormedPrev = _longSma.IsFormed;
// ...
});
}
避免使用同步对象
由于所有事件都在单线程中处理,无需使用同步对象:
// 正确:无需同步的常规处理
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// 错误:不必要的同步
private readonly object _syncLock = new object(); // Not needed
private void ProcessCandle(ICandleMessage candle)
{
lock (_syncLock) // Not needed
{
var ls = _longSma.Process(candle);
// ...
}
}
外部资源
使用 StockSharp 基础设施
不要直接访问外部资源(文件、数据库、网络),而应使用 StockSharp 平台提供的功能:
// 正确:使用内置机制保存数据
protected override void OnStopped()
{
// 数据会通过策略参数自动保存
base.OnStopped();
}
// 错误:直接访问外部资源
protected override void OnStopped()
{
// 不要这样做
File.WriteAllText("results.txt", $"PnL: {PnL}");
// or this
using (var connection = new SqlConnection("..."))
{
// ...
}
base.OnStopped();
}
数据存储
要保存策略结果,请使用:
保存与加载方法
Strategy.Save 和 Strategy.Load 方法专门用于保存不是设置或参数的额外策略数据。这是保存恢复策略状态所需数据的理想位置:
public override void Save(SettingsStorage settings)
{
base.Save(settings); // 先保存策略参数
// 然后保存自定义数据
settings.SetValue("CustomState", _customState);
settings.SetValue("LastSignalTime", _lastSignalTime);
}
public override void Load(SettingsStorage settings)
{
base.Load(settings); // 先加载策略参数
// 然后加载自定义数据
if (settings.Contains("CustomState"))
_customState = settings.GetValue<string>("CustomState");
if (settings.Contains("LastSignalTime"))
_lastSignalTime = settings.GetValue<DateTimeOffset>("LastSignalTime");
}
然而,主要的可配置参数仍应通过上述描述的 StrategyParam<T> 实现,因为这将确保它们在用户界面中自动显示。
市场数据订阅
使用规则而不是直接订阅
对于市场数据处理,建议使用 事件模型 和规则:
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
var subscription = new Subscription(序列, Security);
// 正确:使用规则处理数据
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Connector.Subscribe(subscription);
}
规则相比普通事件处理程序有几个重要的优势:
自动退订 - 当策略停止或不再需要时,规则会自动从事件中退订。你无需手动管理订阅。
高级 API - 规则提供了比标准事件处理程序更易理解和方便的接口。例如,
WhenCandlesFinished比订阅CandleReceived事件并随后检查K线状态要清晰得多。条件组合 - 规则可以使用
And、Or等运算符组合,从而创建复杂的激活条件:
// 组合规则示例
Security
.WhenNewTrade()
.And(Portfolio.WhenMoneyChanged())
.Do(() => {
// 仅当有新成交
// 且投资组合余额发生变化时执行的代码
})
.Apply(this);
- 生命周期管理 - 规则可以设置为一次性(
Once())、设定取消条件(Until())、添加延迟操作等。
兼容策略示例
下面是一个遵循所有建议并能在所有 StockSharp 平台上正确运行的策略示例:
public class SmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _series;
private readonly StrategyParam<int> _longSmaLength;
private readonly StrategyParam<int> _shortSmaLength;
public DataType 序列
{
get => _series.Value;
set => _series.Value = value;
}
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public int ShortSmaLength
{
get => _shortSmaLength.Value;
set => _shortSmaLength.Value = value;
}
private SimpleMovingAverage _longSma;
private SimpleMovingAverage _shortSma;
private IChart _chart;
private IChartCandleElement _chartCandleElement;
private IChartIndicatorElement _longSmaIndicatorElement;
private IChartIndicatorElement _shortSmaIndicatorElement;
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("长期 SMA 周期", string.Empty, "基本设置")
.SetCanOptimize(true);
_shortSmaLength = Param(nameof(ShortSmaLength), 30)
.SetDisplay("短期 SMA 周期", string.Empty, "基本设置")
.SetCanOptimize(true);
_series = Param(nameof(序列), DataType.TimeFrame(TimeSpan.FromMinutes(15)))
.SetDisplay("序列", string.Empty, "基本设置");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
// 如果可用则初始化图表
_chart = GetChart();
if (_chart != null)
InitChart();
var subscription = new Subscription(序列, Security);
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Connector.Subscribe(subscription);
}
private void InitChart()
{
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
_longSmaIndicatorElement = area.AddIndicator(_longSma);
_longSmaIndicatorElement.Color = System.Drawing.Color.Brown;
_longSmaIndicatorElement.DrawStyle = DrawStyles.Line;
_shortSmaIndicatorElement = area.AddIndicator(_shortSma);
_shortSmaIndicatorElement.Color = System.Drawing.Color.Blue;
_shortSmaIndicatorElement.DrawStyle = DrawStyles.Line;
}
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// 如果可用则在图表上绘制
if (_chart != null)
{
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, ls)
.Add(_shortSmaIndicatorElement, ss);
_chart.Draw(data);
}
if (!_longSma.IsFormed)
return;
var isShortLessCurrent = _shortSma.GetCurrentValue() < _longSma.GetCurrentValue();
var isShortLessPrev = _shortSma.GetValue(1) < _longSma.GetValue(1);
if (isShortLessCurrent == isShortLessPrev)
return;
// 交易逻辑
var volume = Volume + Math.Abs(Position);
if (isShortLessCurrent)
SellMarket(volume);
else
BuyMarket(volume);
}
}