Renko Level 策略
概述
Renko Level 策略是对 MetaTrader 5 中 “Renko Level EA” 的完整移植。策略在 StockSharp 中重建了原始 EA 使用的自定义指标逻辑,当四舍五入后的 Renko 水平跳到新的区间时立即交易。每一次水平变化都被视为合成 Renko 砖块的突破,默认沿突破方向建仓,开启反向模式后则反向交易。
策略只需要常规的时间周期 K 线(默认 1 分钟)作为数据源。每根收盘价都会按配置的砖块大小进行四舍五入,从而模拟 Renko 砖块而无需订阅 Renko 数据。一旦四舍五入后的水平发生变化,策略会先平掉相反方向的仓位,再按照新方向开仓。
交易逻辑
- 初始化
- 从品种元数据中读取
PriceStep(最小报价步长),自动识别点值。
- 将
Block Size 参数从点数换算成价格单位;对于 3 位或 5 位小数品种,自动乘以 10 以获取标准点值。
- 在第一根完成的 K 线上,把收盘价四舍五入到最近的砖块中心,生成初始的上边界和下边界。
- 水平维护
- 每根完成 K 线时将收盘价按砖块大小四舍五入。
- 收盘价仍在当前区间内时,保持当前上下边界不变。
- 收盘价跌破下边界时,将价格向下取整并把砖块整体下移(
lower = round,upper = round + size)。
- 收盘价突破上边界时,将价格向上取整并把砖块整体上移(
upper = round,lower = round - size)。
- 信号生成
- 上边界上移代表 Renko 区间向上突破,下移代表向下突破。
Reverse 关闭时,向上突破买入、向下突破卖出;开启 Reverse 后逻辑完全互换。
- 触发信号时会先用市价单平掉相反方向仓位。如果
Allow Increase 为 false,且当前已有同向持仓,策略不会再加仓。
- 下单方式
- 下单数量取自策略的
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 决定。
- 品种最好提供
PriceStep 与 Decimals。若缺失,则退回到 0.0001 的默认步长。
推荐流程
- 在 Designer 中添加策略,或通过代码实例化
RenkoLevelStrategy。
- 选择
Security、Portfolio,设置下单 Volume,根据需要调整其它参数。
- 启动策略,它会等待第一根完成的 K 线来建立初始 Renko 区间。
- 通过内置图表或日志观察,确认只有当四舍五入后的水平变化时才会触发订单。
本文档详细说明了原版 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class renko_level_strategy(Strategy):
def __init__(self):
super(renko_level_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._block_size = self.Param("BlockSize", 5000) \
.SetDisplay("Block Size", "Renko block size in price steps", "Renko")
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def BlockSize(self):
return self._block_size.Value
def OnReseted(self):
super(renko_level_strategy, self).OnReseted()
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
def OnStarted2(self, time):
super(renko_level_strategy, self).OnStarted2(time)
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
brick_size = self._get_brick_size()
if brick_size <= 0:
return
close = float(candle.ClosePrice)
if not self._has_levels:
self._initialize_levels(close, brick_size)
return
previous_upper = self._upper_level
moved = False
if close < self._lower_level:
rnd, _, ceil = self._calculate_levels(close, brick_size)
if abs(rnd - self._lower_level) > brick_size * 0.01:
self._lower_level = rnd
self._upper_level = ceil
moved = True
elif close > self._upper_level:
rnd, floor, _ = self._calculate_levels(close, brick_size)
if abs(rnd - self._upper_level) > brick_size * 0.01:
self._lower_level = floor
self._upper_level = rnd
moved = True
if not moved:
return
if self._upper_level > previous_upper and self.Position <= 0:
self.BuyMarket()
elif self._upper_level < previous_upper and self.Position >= 0:
self.SellMarket()
def _initialize_levels(self, price, brick_size):
rnd, floor, _ = self._calculate_levels(price, brick_size)
self._upper_level = rnd
self._lower_level = floor
self._has_levels = True
def _calculate_levels(self, price, brick_size):
ratio = price / brick_size
rounded = round(ratio)
price_round = rounded * brick_size
price_floor = price_round - brick_size
price_ceil = price_round + brick_size
return (price_round, price_floor, price_ceil)
def _get_brick_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
return step * self.BlockSize
def CreateClone(self):
return renko_level_strategy()