XMA Ichimoku Channel 策略
概述
该策略基于 XMA Ichimoku 概念构建通道突破系统。它在最近高点和低点的平滑平均值周围构建动态通道,当价格确认突破并回撤时生成交易。
运行原理
- 最高值与最低值:对每根完成的蜡烛计算在设定周期内的最高价和最低价。
- 平滑中线:最高价与最低价的中点通过简单移动平均线进行平滑。
- 通道构建:通过在平滑中线上应用百分比偏移来得到上轨和下轨。
- 交易逻辑:
- 如果前一收盘价在上轨之上且当前收盘价回落到当前上轨下方,则关闭空头并开多。
- 如果前一收盘价在下轨之下且当前收盘价回升到当前下轨上方,则关闭多头并开空。
参数
- Up Period – 计算最高价的回溯周期。
- Down Period – 计算最低价的回溯周期。
- MA Length – 平滑移动平均线长度。
- Up Percent – 上轨相对于中线的百分比偏移。
- Down Percent – 下轨相对于中线的百分比偏移。
- Candle Type – 使用的蜡烛时间框架。
使用说明
- 使用市价单执行交易。
- 仅处理已完成的蜡烛以避免虚假信号。
- 在开新仓前会先平掉相反方向的仓位。
免责声明
本示例仅供学习参考,实盘使用前请充分测试。
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the XMA Ichimoku channel concept.
/// </summary>
public class XmaIchimokuChannelStrategy : Strategy
{
private readonly StrategyParam<int> _upPeriod;
private readonly StrategyParam<int> _downPeriod;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _upPercent;
private readonly StrategyParam<decimal> _downPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private readonly SimpleMovingAverage _sma = new();
private bool _isInitialized;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _prevClose;
/// <summary>
/// Period for calculating the highest value.
/// </summary>
public int UpPeriod
{
get => _upPeriod.Value;
set => _upPeriod.Value = value;
}
/// <summary>
/// Period for calculating the lowest value.
/// </summary>
public int DownPeriod
{
get => _downPeriod.Value;
set => _downPeriod.Value = value;
}
/// <summary>
/// Length of the smoothing moving average.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Percentage above the average for the upper band.
/// </summary>
public decimal UpPercent
{
get => _upPercent.Value;
set => _upPercent.Value = value;
}
/// <summary>
/// Percentage below the average for the lower band.
/// </summary>
public decimal DownPercent
{
get => _downPercent.Value;
set => _downPercent.Value = value;
}
/// <summary>
/// Type of candles to subscribe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="XmaIchimokuChannelStrategy"/>.
/// </summary>
public XmaIchimokuChannelStrategy()
{
_upPeriod = Param(nameof(UpPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Up Period", "Lookback for high prices", "Channel");
_downPeriod = Param(nameof(DownPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Down Period", "Lookback for low prices", "Channel");
_maLength = Param(nameof(MaLength), 100)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Smoothing length", "Channel");
_upPercent = Param(nameof(UpPercent), 1m)
.SetDisplay("Up Percent", "Upper band offset in %", "Channel");
_downPercent = Param(nameof(DownPercent), 1m)
.SetDisplay("Down Percent", "Lower band offset in %", "Channel");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_sma.Length = MaLength;
_sma.Reset();
_isInitialized = false;
_prevUpper = 0m;
_prevLower = 0m;
_prevClose = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma.Length = MaLength;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_highs.Enqueue(candle.HighPrice);
if (_highs.Count > UpPeriod)
_highs.Dequeue();
_lows.Enqueue(candle.LowPrice);
if (_lows.Count > DownPeriod)
_lows.Dequeue();
if (_highs.Count < UpPeriod || _lows.Count < DownPeriod)
return;
var highestValue = GetMax(_highs);
var lowestValue = GetMin(_lows);
var midValue = (highestValue + lowestValue) / 2m;
var middle = _sma.Process(new DecimalIndicatorValue(_sma, midValue, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_sma.IsFormed)
return;
var upper = middle * (1m + UpPercent / 100m);
var lower = middle * (1m - DownPercent / 100m);
if (!_isInitialized)
{
_prevUpper = upper;
_prevLower = lower;
_prevClose = candle.ClosePrice;
_isInitialized = true;
return;
}
if (_prevClose > _prevUpper && candle.ClosePrice <= upper && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (_prevClose < _prevLower && candle.ClosePrice >= lower && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
_prevUpper = upper;
_prevLower = lower;
_prevClose = candle.ClosePrice;
}
private static decimal GetMax(IEnumerable<decimal> values)
{
var result = decimal.MinValue;
foreach (var value in values)
{
if (value > result)
result = value;
}
return result;
}
private static decimal GetMin(IEnumerable<decimal> values)
{
var result = decimal.MaxValue;
foreach (var value in values)
{
if (value < result)
result = value;
}
return result;
}
}
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 collections import deque
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class xma_ichimoku_channel_strategy(Strategy):
def __init__(self):
super(xma_ichimoku_channel_strategy, self).__init__()
self._up_period = self.Param("UpPeriod", 3) \
.SetDisplay("Up Period", "Lookback for high prices", "Channel")
self._down_period = self.Param("DownPeriod", 3) \
.SetDisplay("Down Period", "Lookback for low prices", "Channel")
self._ma_length = self.Param("MaLength", 100) \
.SetDisplay("MA Length", "Smoothing length", "Channel")
self._up_percent = self.Param("UpPercent", 1.0) \
.SetDisplay("Up Percent", "Upper band offset in %", "Channel")
self._down_percent = self.Param("DownPercent", 1.0) \
.SetDisplay("Down Percent", "Lower band offset in %", "Channel")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._highs = deque()
self._lows = deque()
self._sma = None
self._is_initialized = False
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_close = 0.0
@property
def UpPeriod(self):
return self._up_period.Value
@UpPeriod.setter
def UpPeriod(self, value):
self._up_period.Value = value
@property
def DownPeriod(self):
return self._down_period.Value
@DownPeriod.setter
def DownPeriod(self, value):
self._down_period.Value = value
@property
def MaLength(self):
return self._ma_length.Value
@MaLength.setter
def MaLength(self, value):
self._ma_length.Value = value
@property
def UpPercent(self):
return self._up_percent.Value
@UpPercent.setter
def UpPercent(self, value):
self._up_percent.Value = value
@property
def DownPercent(self):
return self._down_percent.Value
@DownPercent.setter
def DownPercent(self, value):
self._down_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(xma_ichimoku_channel_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.MaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._highs.append(float(candle.HighPrice))
if len(self._highs) > self.UpPeriod:
self._highs.popleft()
self._lows.append(float(candle.LowPrice))
if len(self._lows) > self.DownPeriod:
self._lows.popleft()
if len(self._highs) < self.UpPeriod or len(self._lows) < self.DownPeriod:
return
highest = max(self._highs)
lowest = min(self._lows)
mid_value = (highest + lowest) / 2.0
result = process_float(self._sma, mid_value, candle.OpenTime, True)
middle = float(result)
if not self._sma.IsFormed:
return
upper = middle * (1.0 + float(self.UpPercent) / 100.0)
lower = middle * (1.0 - float(self.DownPercent) / 100.0)
if not self._is_initialized:
self._prev_upper = upper
self._prev_lower = lower
self._prev_close = float(candle.ClosePrice)
self._is_initialized = True
return
close = float(candle.ClosePrice)
if self._prev_close > self._prev_upper and close <= upper and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self._prev_close < self._prev_lower and close >= lower and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._prev_upper = upper
self._prev_lower = lower
self._prev_close = close
def OnReseted(self):
super(xma_ichimoku_channel_strategy, self).OnReseted()
self._highs = deque()
self._lows = deque()
if self._sma is not None:
self._sma.Length = self.MaLength
self._sma.Reset()
self._is_initialized = False
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_close = 0.0
def CreateClone(self):
return xma_ichimoku_channel_strategy()