Darvas Boxes 系统策略
概述
该策略基于经典的 Darvas Boxes 概念,采用突破交易方式。利用 Donchian Channels 指标构建动态价格区间。当价格收盘高于箱体上边界时开多单;当价格收盘低于箱体下边界时开空单。同时可以设置止损和止盈来管理风险。
工作原理
- 对于每根 K 线,Donchian Channels 根据参数
BoxPeriod计算上下边界。 - 策略跟踪上一根 K 线的边界值以检测突破。
- 如果当前收盘价突破上一根 K 线的上边界:
- 如果允许,平掉现有空单。
- 如果允许,开立新的多单。
- 如果当前收盘价跌破上一根 K 线的下边界:
- 如果允许,平掉现有多单。
- 如果允许,开立新的空单。
- 对持仓持续监控,达到止损或止盈条件时退出。
参数
- BoxPeriod (
int): 构建箱体所使用的 K 线数量,默认 20。 - StopLoss (
decimal): 入场价到止损位的距离,默认 1000。 - TakeProfit (
decimal): 入场价到止盈位的距离,默认 2000。 - AllowBuyEntry (
bool): 是否允许开多,默认true。 - AllowSellEntry (
bool): 是否允许开空,默认true。 - AllowBuyExit (
bool): 是否允许在反向信号或风险事件下平多,默认true。 - AllowSellExit (
bool): 是否允许在反向信号或风险事件下平空,默认true。 - CandleType (
DataType): 用于计算的 K 线类型,默认 4 小时 K 线。
使用方法
- 将策略附加到交易标的并设置参数。
- 启动策略,系统会订阅所需的 K 线并开始处理数据。
- 当出现突破信号时,策略会以市价单执行交易。
- 止损和止盈用于管理持仓风险。
说明
- 策略使用
BindEx连接指标与 K 线数据,属于高层 API 的实现。 - 不使用额外的内部集合,所有计算基于回调提供的值完成。
- 仅处理已完成的 K 线以确保信号可靠。
- 按要求,代码中的注释为英文。
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>
/// Darvas Boxes breakout strategy using Donchian Channels.
/// Opens long position when price breaks above the upper box line.
/// Opens short position when price breaks below the lower box line.
/// </summary>
public class DarvasBoxesSystemStrategy : Strategy
{
private readonly StrategyParam<int> _boxPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _prevClose;
public int BoxPeriod
{
get => _boxPeriod.Value;
set => _boxPeriod.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DarvasBoxesSystemStrategy()
{
_boxPeriod = Param(nameof(BoxPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Box Period", "Period for box calculation", "Indicators")
.SetOptimize(10, 40, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevUpper = 0m;
_prevLower = 0m;
_prevClose = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevUpper = 0m;
_prevLower = 0m;
_prevClose = 0m;
var donchian = new DonchianChannels { Length = BoxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (value is not IDonchianChannelsValue box)
return;
if (box.UpperBand is not decimal upper || box.LowerBand is not decimal lower)
return;
if (_prevUpper == 0m)
{
_prevUpper = upper;
_prevLower = lower;
_prevClose = candle.ClosePrice;
return;
}
var isUpBreakout = candle.ClosePrice > _prevUpper && _prevClose <= _prevUpper;
var isDownBreakout = candle.ClosePrice < _prevLower && _prevClose >= _prevLower;
if (isUpBreakout && Position <= 0)
BuyMarket();
else if (isDownBreakout && Position >= 0)
SellMarket();
_prevUpper = upper;
_prevLower = lower;
_prevClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels
from StockSharp.Algo.Strategies import Strategy
class darvas_boxes_system_strategy(Strategy):
def __init__(self):
super(darvas_boxes_system_strategy, self).__init__()
self._box_period = self.Param("BoxPeriod", 20) \
.SetDisplay("Box Period", "Period for box calculation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_close = 0.0
@property
def box_period(self):
return self._box_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(darvas_boxes_system_strategy, self).OnReseted()
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_close = 0.0
def OnStarted2(self, time):
super(darvas_boxes_system_strategy, self).OnStarted2(time)
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_close = 0.0
donchian = DonchianChannels()
donchian.Length = self.box_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(donchian, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
def process_candle(self, candle, value):
if candle.State != CandleStates.Finished:
return
upper = value.UpperBand
lower = value.LowerBand
if upper is None or lower is None:
return
upper = float(upper)
lower = float(lower)
close_price = float(candle.ClosePrice)
if self._prev_upper == 0.0:
self._prev_upper = upper
self._prev_lower = lower
self._prev_close = close_price
return
is_up_breakout = close_price > self._prev_upper and self._prev_close <= self._prev_upper
is_down_breakout = close_price < self._prev_lower and self._prev_close >= self._prev_lower
if is_up_breakout and self.Position <= 0:
self.BuyMarket()
elif is_down_breakout and self.Position >= 0:
self.SellMarket()
self._prev_upper = upper
self._prev_lower = lower
self._prev_close = close_price
def CreateClone(self):
return darvas_boxes_system_strategy()