在 GitHub 上查看

Renko 图表策略

概述

RenkoChartStrategy 是对 RenkoChart.mq5 专家的转换版本。该策略不会发出订单,而是在 StockSharp 环境中 重现自定义 Renko 符号的构建流程。策略订阅行情 tick,按照可配置的砖块尺寸生成 Renko 蜡烛序列,并将其提供给 平台用于可视化或进一步处理。每一个完成的砖块都会记录触发它的最新 tick,便于与 MetaTrader 中的结果进行对比。

MQL 参数映射

  • StartDateTimeStartTime:启动 Renko 历史构建时使用的初始时间。
  • BaseSymbolStrategy.Security:在 StockSharp 中基础标的由连接器提供,因此直接使用当前分配的 Security。 为了保留 “Renko-<symbol>” 的命名习惯,日志与图表标题会使用 RenkoPrefix 作为前缀。
  • Mode (Bid/Last)UseBidTicks:选择监控买价还是成交价的 tick 流,与 MQL 中的模式相对应。
  • RangeBrickSizeSteps:构成一个砖块所需的价格步数,最终会乘以 PriceStep 得到实际的砖块高度。

参数

名称 类型 默认值 说明
StartTime DateTimeOffset 2018-08-01 09:00:00 UTC 开盘时间早于该值的砖块会被忽略,模拟原版的预热行为。
BrickSizeSteps int 5 以价格步数表示的砖块尺寸,创建订阅时会转换为绝对价格。
UseBidTicks bool false false 表示监听成交价,true 表示监听买价(Bid 模式)。
RenkoPrefix string "Renko-" 用于日志和图表标题的虚拟 Renko 符号前缀。

提示: 属性 BrickSize 提供了绝对砖块高度,可供需要实际价格差的其他模块使用。

工作流程

  1. GetWorkingSecurities 基于 RenkoBuildFrom.Points 和计算出的砖块高度创建 Renko 蜡烛订阅。
  2. OnStarted 启动 Renko 订阅,根据 UseBidTicks 选择订阅买价或成交价 tick,并在图表可用时绘制 Renko 序列。
  3. ProcessTrade / ProcessLevel1 保存最新的 tick 价格和时间戳,以便在日志中显示。
  4. ProcessCandle 过滤掉未完成的砖块和早于 StartTime 的数据,记录每个完成的砖块、前一个收盘价、新收盘价以及触发 tick。

使用建议

  • 将策略附加到能够提供成交或 Level1 数据的标的上。Renko 序列会在默认图表区域以指定前缀展示。
  • 策略不进行交易,可与其他策略并行运行,用作 Renko 视角的行情监控组件。
  • 日志同时包含砖块方向与触发 tick,方便与 MetaTrader 的历史导出结果进行比对。

与原版的差异

  • 不再手动创建自定义符号,而是通过 StockSharp 的订阅和图表系统输出数据并提供详细日志。
  • 使用内置的 Renko 蜡烛生成器处理 decimal 精度,避免了手动维护数组。
  • 充分利用 StockSharp 的订阅模型和保护机制,如需扩展为交易策略可以直接添加下单逻辑。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Renko Chart strategy. Uses WMA crossover for trend detection.
/// </summary>
public class RenkoChartStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public RenkoChartStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("Fast WMA", "Fast WMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = null;
		_prevSlow = null;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fastVal > slowVal;

		_prevFast = fastVal;
		_prevSlow = slowVal;

		if (!prevAbove && currAbove && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (prevAbove && !currAbove && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}