C# 策略示例

下面以 SMA 策略为例,演示如何通过源代码创建策略。该示例与通过模块创建算法中使用模块组装的 SMA 策略相似。

本节不介绍 C# 语言结构,也不详细说明作为策略基类的 Strategy,而是重点介绍在 Designer 中使用代码时需要注意的特性。

Tip

Designer 中创建的策略与通过 API 创建的策略兼容,因为二者使用相同的基类 Strategy。因此,与运行策略图相比,在 Designer 外部运行此类策略要简单得多。

  1. 使用专门的方式创建策略参数:
_candleTypeParam = this.Param(nameof(CandleType), DataType.TimeFrame(TimeSpan.FromMinutes(1)));
_long = this.Param(nameof(Long), 80);
_short = this.Param(nameof(Short), 20);


private readonly StrategyParam<DataType> _candleTypeParam;

public DataType CandleType
{
	get => _candleTypeParam.Value;
	set => _candleTypeParam.Value = value;
}

private readonly StrategyParam<int> _long;

public int Long
{
	get => _long.Value;
	set => _long.Value = value;
}

private readonly StrategyParam<int> _short;

public int Short
{
	get => _short.Value;
	set => _short.Value = value;
}

使用 StrategyParam 类后,系统会自动处理设置的保存和恢复。

  1. 创建指标并订阅市场数据时,需要将二者绑定,使订阅收到的数据能够更新指标值:
// ---------- 创建指标 -----------

var longSma = new SMA { Length = Long };
var shortSma = new SMA { Length = Short };

// ----------------------------------------

// --- 绑定K线集和指标 ----

var subscription = SubscribeCandles(CandleType);

subscription
	// 将指标绑定到K线
	.Bind(longSma, shortSma, OnProcess)
	// 开始处理
	.Start();
  1. 使用图表时需要注意:在 Designer 外部运行策略时,图表对象可能不存在。
var area = CreateChartArea();

// 如果没有 GUI,area 可以为 null(策略运行在 Runner 或自己的控制台应用中)
if (area != null)
{
	DrawCandles(area, subscription);

	DrawIndicator(area, shortSma, System.Drawing.Color.Coral);
	DrawIndicator(area, longSma);

	DrawOwnTrades(area);
}
  1. 如果策略逻辑需要,请通过 StartProtection 启动持仓保护:
// 按 take profit 和/或 stop loss 启动保护
StartProtection(TakeValue, StopValue);
  1. 策略逻辑本身在 OnProcess 方法中实现。该方法由步骤 1 中创建的订阅调用:
private void OnProcess(ICandleMessage candle, decimal longValue, decimal shortValue)
{
	LogInfo(LocalizedStrings.SmaNewCandleLog, candle.OpenTime, candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice, candle.TotalVolume, candle.SecurityId);

	// 如果我们只订阅了未完成的K线
	if (candle.State != CandleStates.Finished)
		return;

	// 计算 short 和 long 的新值
	var isShortLessThenLong = shortValue < longValue;

	if (_isShortLessThenLong == null)
	{
		_isShortLessThenLong = isShortLessThenLong;
	}
	else if (_isShortLessThenLong != isShortLessThenLong)
	{
		// 发生交叉

		// if short less than long, the sale, otherwise buy
		var direction = isShortLessThenLong ? Sides.Sell : Sides.Buy;

		// 计算开仓或反转的数量
		var volume = Position == 0 ? Volume : Position.Abs().Min(Volume) * 2;

		var priceStep = GetSecurity().PriceStep ?? 1;

		// 将订单价格计算为收盘价 + 偏移
		var price = candle.ClosePrice + (direction == Sides.Buy ? priceStep : -priceStep);

		if (direction == Sides.Buy)
			BuyLimit(price, volume);
		else
			SellLimit(price, volume);

		// 保存 short 和 long 的当前值
		_isShortLessThenLong = isShortLessThenLong;
	}
}