在 GitHub 上查看

Renko Level 策略

概述

Renko Level 策略是对 MetaTrader 5 中 “Renko Level EA” 的完整移植。策略在 StockSharp 中重建了原始 EA 使用的自定义指标逻辑,当四舍五入后的 Renko 水平跳到新的区间时立即交易。每一次水平变化都被视为合成 Renko 砖块的突破,默认沿突破方向建仓,开启反向模式后则反向交易。

策略只需要常规的时间周期 K 线(默认 1 分钟)作为数据源。每根收盘价都会按配置的砖块大小进行四舍五入,从而模拟 Renko 砖块而无需订阅 Renko 数据。一旦四舍五入后的水平发生变化,策略会先平掉相反方向的仓位,再按照新方向开仓。

交易逻辑

  1. 初始化
    • 从品种元数据中读取 PriceStep(最小报价步长),自动识别点值。
    • Block Size 参数从点数换算成价格单位;对于 3 位或 5 位小数品种,自动乘以 10 以获取标准点值。
    • 在第一根完成的 K 线上,把收盘价四舍五入到最近的砖块中心,生成初始的上边界和下边界。
  2. 水平维护
    • 每根完成 K 线时将收盘价按砖块大小四舍五入。
    • 收盘价仍在当前区间内时,保持当前上下边界不变。
    • 收盘价跌破下边界时,将价格向下取整并把砖块整体下移(lower = roundupper = round + size)。
    • 收盘价突破上边界时,将价格向上取整并把砖块整体上移(upper = roundlower = round - size)。
  3. 信号生成
    • 上边界上移代表 Renko 区间向上突破,下移代表向下突破。
    • Reverse 关闭时,向上突破买入、向下突破卖出;开启 Reverse 后逻辑完全互换。
    • 触发信号时会先用市价单平掉相反方向仓位。如果 Allow Increasefalse,且当前已有同向持仓,策略不会再加仓。
  4. 下单方式
    • 下单数量取自策略的 Volume 属性。若需要翻仓,会自动将下单量设置为“当前绝对持仓 + Volume”,从而一次性完成平仓与开仓。
    • 启动时调用 StartProtection(),方便通过 Designer 或组合策略启用风险保护模块。

参数说明

参数 说明 默认值
Block Size 以“点”为单位的 Renko 砖块大小。策略会乘以品种点值得到实际价格增量,数值越大信号越少。 30
Reverse 设为 true 时反向执行所有信号(上移做空,下移做多)。 false
Allow Increase 设为 true 时允许顺势加仓;设为 false 时只有在完全平仓后才会发送新订单。 false
Candle Type 数据源类型,默认订阅 1 分钟时间框。可替换为任何兼容的 DataType,包括真实 Renko 数据。 TimeFrame(1m)
Volume (继承) 下单手数。启动前请在策略实例上设置合适的数量。 由账户决定

使用建议

  • 根据标的波动性选择砖块大小。主流外汇货币对通常使用 30–50 点,指数或加密资产可选择更大的数值。

  • 策略适用于任意 K 线源(时间、成交量、Range 等),只要收盘价反映了想要的采样频率。如果直接使用 Renko 数据,可将 Candle Type 切换为 Renko 系列。

  • 启用 Reverse 可以把突破系统改造成逆势系统,尝试在每次水平变化时做反向单。

  • 开启 Allow Increase 能模仿原始 EA 的 “Increase” 选项,在同方向信号出现时持续加仓。

  • 止损、止盈和风险控制可通过 StockSharp 的保护模块或外部包装策略设置。本示例保持与原 EA 相同的退出机制,只在水平翻转时离场。

数据需求

  • 需要历史与实时的蜡烛线数据,频率由 Candle Type 决定。
  • 品种最好提供 PriceStepDecimals。若缺失,则退回到 0.0001 的默认步长。

推荐流程

  1. 在 Designer 中添加策略,或通过代码实例化 RenkoLevelStrategy
  2. 选择 SecurityPortfolio,设置下单 Volume,根据需要调整其它参数。
  3. 启动策略,它会等待第一根完成的 K 线来建立初始 Renko 区间。
  4. 通过内置图表或日志观察,确认只有当四舍五入后的水平变化时才会触发订单。

本文档详细说明了原版 Renko Level EA 在 StockSharp 中的实现方式,方便你在此基础上进行定制或扩展。

using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Renko level breakout strategy. Emulates Renko bricks on time-based candles
/// and trades when the rounded level shifts up or down.
/// </summary>
public class RenkoLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _blockSize;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _upperLevel;
	private decimal _lowerLevel;
	private bool _hasLevels;

	public int BlockSize
	{
		get => _blockSize.Value;
		set => _blockSize.Value = value;
	}

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

	public RenkoLevelStrategy()
	{
		_blockSize = Param(nameof(BlockSize), 5000)
			.SetGreaterThanZero()
			.SetDisplay("Block Size", "Renko block size in price steps", "Renko");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_upperLevel = 0m;
		_lowerLevel = 0m;
		_hasLevels = false;
	}

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

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

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

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

		var brickSize = GetBrickSize();
		if (brickSize <= 0m)
			return;

		var close = candle.ClosePrice;

		if (!_hasLevels)
		{
			InitializeLevels(close, brickSize);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var previousUpper = _upperLevel;
		var moved = false;

		if (close < _lowerLevel)
		{
			var (round, _, ceil) = CalculateLevels(close, brickSize);
			if (Math.Abs(round - _lowerLevel) > brickSize * 0.01m)
			{
				_lowerLevel = round;
				_upperLevel = ceil;
				moved = true;
			}
		}
		else if (close > _upperLevel)
		{
			var (round, floor, _) = CalculateLevels(close, brickSize);
			if (Math.Abs(round - _upperLevel) > brickSize * 0.01m)
			{
				_lowerLevel = floor;
				_upperLevel = round;
				moved = true;
			}
		}

		if (!moved)
			return;

		if (_upperLevel > previousUpper && Position <= 0)
			BuyMarket();
		else if (_upperLevel < previousUpper && Position >= 0)
			SellMarket();
	}

	private void InitializeLevels(decimal price, decimal brickSize)
	{
		var (round, floor, _) = CalculateLevels(price, brickSize);
		_upperLevel = round;
		_lowerLevel = floor;
		_hasLevels = true;
	}

	private (decimal round, decimal floor, decimal ceil) CalculateLevels(decimal price, decimal brickSize)
	{
		var ratio = price / brickSize;
		var rounded = Math.Round(ratio, 0, MidpointRounding.AwayFromZero);
		var priceRound = rounded * brickSize;
		var priceFloor = priceRound - brickSize;
		var priceCeil = priceRound + brickSize;
		return (priceRound, priceFloor, priceCeil);
	}

	private decimal GetBrickSize()
	{
		var step = Security?.PriceStep ?? 0.01m;
		return step * BlockSize;
	}
}