Dekidaka-Ashi 蜡烛与成交量策略
该策略使用 Dekidaka-Ashi 方法,根据成交量平滑调整蜡烛范围。出现看涨信号时做多,出现看跌信号时做空。上下界同时被突破的蜡烛将平掉已有仓位。
细节
- 入场条件:
- 强或弱看涨信号:最高价高于上界且最低价高于下界。
- 强或弱看跌信号:最高价低于上界且最低价低于下界。
- 多空方向:双向。
- 出场条件:
- 反向信号或同时穿越上下界的蜡烛(不确定)。
- 止损:无。
- 默认值:
BodySize= 1VolumeSmooth= 1CandleType= 5 分钟
- 筛选:
- 类型:形态
- 方向:多头与空头
- 指标:EMA、成交量
- 止损:否
- 复杂度:低
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中
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>
/// Dekidaka-Ashi candles volume strategy.
/// The range is the body of the previous candle, scaled around its middle by BodySize and by the ratio of that candle's volume
/// to its VolumeSmooth EMA, so heavy candles give a wider range.
/// A candle with its high above the upper bound and its low above the lower bound is bullish and goes long; one with its high below
/// the upper bound and its low below the lower bound is bearish and goes short, reversing an opposite position.
/// A candle spanning both bounds closes any position.
/// </summary>
public class DekidakaAshiCandlesVolumeStrategy : Strategy
{
private readonly StrategyParam<decimal> _bodySize;
private readonly StrategyParam<int> _volumeSmooth;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _volumeEma;
private decimal? _prevUpper;
private decimal? _prevLower;
/// <summary>
/// Multiplier of the body range.
/// </summary>
public decimal BodySize
{
get => _bodySize.Value;
set => _bodySize.Value = value;
}
/// <summary>
/// EMA length of the volume smoothing.
/// </summary>
public int VolumeSmooth
{
get => _volumeSmooth.Value;
set => _volumeSmooth.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DekidakaAshiCandlesVolumeStrategy()
{
_bodySize = Param(nameof(BodySize), 1m)
.SetGreaterThanZero()
.SetDisplay("Body Size", "Multiplier of the body range", "Range");
_volumeSmooth = Param(nameof(VolumeSmooth), 1)
.SetGreaterThanZero()
.SetDisplay("Volume Smooth", "EMA length of the volume smoothing", "Range");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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 = null;
_prevLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevUpper = null;
_prevLower = null;
_volumeEma = new ExponentialMovingAverage { Length = VolumeSmooth };
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 upper = _prevUpper;
var lower = _prevLower;
var smoothed = _volumeEma.Process(candle.TotalVolume, candle.ServerTime, true);
if (smoothed.IsFormed)
{
var avgVolume = smoothed.GetValue<decimal>();
var ratio = avgVolume > 0 ? candle.TotalVolume / avgVolume : 1m;
var middle = (candle.OpenPrice + candle.ClosePrice) / 2;
var halfBody = Math.Abs(candle.ClosePrice - candle.OpenPrice) / 2 * BodySize * ratio;
_prevUpper = middle + halfBody;
_prevLower = middle - halfBody;
}
if (upper is not decimal up || lower is not decimal low)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullish = candle.HighPrice > up && candle.LowPrice > low;
var bearish = candle.HighPrice < up && candle.LowPrice < low;
var spansBoth = candle.HighPrice >= up && candle.LowPrice <= low;
if (bullish && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (bearish && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (spansBoth && Position > 0)
SellMarket(Position);
else if (spansBoth && Position < 0)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class dekidaka_ashi_candles_volume_strategy(Strategy):
"""
Dekidaka-Ashi candles volume strategy.
The range is the body of the previous candle, scaled around its middle by BodySize and by the ratio of that candle's volume
to its VolumeSmooth EMA, so heavy candles give a wider range.
A candle with its high above the upper bound and its low above the lower bound is bullish and goes long; one with its high below
the upper bound and its low below the lower bound is bearish and goes short, reversing an opposite position.
A candle spanning both bounds closes any position.
"""
def __init__(self):
super(dekidaka_ashi_candles_volume_strategy, self).__init__()
self._body_size = self.Param("BodySize", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Body Size", "Multiplier of the body range", "Range")
self._volume_smooth = self.Param("VolumeSmooth", 1) \
.SetGreaterThanZero() \
.SetDisplay("Volume Smooth", "EMA length of the volume smoothing", "Range")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_ema = None
self._prev_upper = None
self._prev_lower = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(dekidaka_ashi_candles_volume_strategy, self).OnReseted()
self._prev_upper = None
self._prev_lower = None
def OnStarted2(self, time):
super(dekidaka_ashi_candles_volume_strategy, self).OnStarted2(time)
self._prev_upper = None
self._prev_lower = None
self._volume_ema = ExponentialMovingAverage()
self._volume_ema.Length = self._volume_smooth.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
upper = self._prev_upper
lower = self._prev_lower
smoothed = process_float(self._volume_ema, candle.TotalVolume, candle.ServerTime, True)
if smoothed.IsFormed:
avg_volume = float(smoothed.GetValue[Decimal](None))
volume = float(candle.TotalVolume)
ratio = volume / avg_volume if avg_volume > 0 else 1.0
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
middle = (open_price + close) / 2.0
half_body = abs(close - open_price) / 2.0 * float(self._body_size.Value) * ratio
self._prev_upper = middle + half_body
self._prev_lower = middle - half_body
if upper is None or lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
bullish = high > upper and low > lower
bearish = high < upper and low < lower
spans_both = high >= upper and low <= lower
if bullish and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif bearish and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif spans_both and self.Position > 0:
self.SellMarket(self.Position)
elif spans_both and self.Position < 0:
self.BuyMarket(-self.Position)
def CreateClone(self):
return dekidaka_ashi_candles_volume_strategy()