Стратегия GoldWarrior02b
Алгоритмическая стратегия, конвертированная из советника MetaTrader GoldWarrior02b. Комбинирует импульсный индикатор, Commodity Channel Index (CCI) и упрощённый ZigZag для торговли в последние секунды каждого 15-минутного блока.
Реализация использует высокоуровневый API StockSharp и работает с чистой позицией. Многоуровневый хеджинг оригинальной версии не реализован, так как StockSharp оперирует неттинговыми позициями.
Концепция
- Пользовательский индикатор импульса усредняет разницу между ценами открытия и закрытия свечей.
- Значения CCI помогают обнаруживать развороты перекупленности/перепроданности и резкие импульсы.
- Направление ZigZag по недавним максимумам/минимумам фильтрует сделки против доминирующего движения.
- Сигналы проверяются только в финальные секунды (>= 45 сек) минут 14, 29, 44 и 59.
- Управление рисками включает стоп, тейк, трейлинг и общий целевой профит.
Правила входа
Сделка рассматривается только при отсутствии открытой позиции и закрытии текущей свечи внутри указанного окна.
Покупка
- Направление ZigZag вниз (текущий минимум ниже предыдущего).
- Выполняется одно из условий:
- CCI растёт относительно предыдущего значения, при этом прежний CCI ниже -50, текущий ниже -30, импульс становится положительным, а прежний импульс был отрицательным.
- Либо CCI опускается ниже -200, прежний CCI ещё ниже, импульс остаётся ниже положительного порога, а предыдущий импульс слабее текущего.
Продажа
- Направление ZigZag вверх (текущий максимум выше предыдущего).
- Выполняется одно из условий:
- CCI падает относительно предыдущего значения, при этом прежний CCI выше 50, текущий выше 30, импульс становится отрицательным, а прежний импульс был положительным.
- Либо CCI поднимается выше 200, предыдущий CCI ещё выше, импульс остаётся выше отрицательного порога, а предыдущий импульс сильнее текущего.
Если предыдущий импульс попадает между порогами покупки и продажи, сигнал игнорируется.
Правила выхода
- Стоп-лосс: закрывает позицию при пересечении уровня стопа.
- Тейк-профит: закрывает позицию при достижении целевого уровня прибыли.
- Трейлинг: после прохождения
(TrailingStop + TrailingStep)пунктов трейлинг фиксируется на расстоянииTrailingStopпунктов. Пересечение уровня закрывает позицию. - Общий профит: закрывает позицию при превышении нереализованной прибыли заданного значения (в валюте счёта).
Параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
BaseVolume |
Размер входа. | 0.1 |
StopLossPoints |
Стоп в пунктах. | 100 |
TakeProfitPoints |
Тейк-профит в пунктах. | 150 |
TrailingStopPoints |
Базовое расстояние трейлинга. | 5 |
TrailingStepPoints |
Дополнительное смещение перед активацией трейлинга. | 5 |
ImpulsePeriod |
Период для CCI и импульса. | 21 |
ZigZagDepth |
Минимум баров между переломами ZigZag. | 12 |
ZigZagDeviation |
Минимальный ход (в пунктах) для подтверждения перелома. | 5 |
ZigZagBackstep |
Минимум баров до принятия нового перелома. | 3 |
ProfitTarget |
Порог нереализованной прибыли для принудительного выхода. | 300 |
ImpulseSellThreshold |
Порог импульса для продаж (обычно отрицательный). | -30 |
ImpulseBuyThreshold |
Порог импульса для покупок (обычно положительный). | 30 |
CandleType |
Таймфрейм расчёта. | 5 минут |
Примечания
- Импульсный индикатор — это скользящее среднее разницы open-close, масштабированное на
PriceStepинструмента. - Для расчёта трейлинга и PnL используются свойства
PriceStepиStepPriceинструмента, переводящие пункты в валюту счёта. - В оригинальном советнике реализованы наращивание объёма и хеджирование. В данной версии поддерживается только одна неттинговая позиция, соответствующая модели StockSharp.
- Для приближения поведения к оригиналу следует подписывать 15-минутные свечи и учитывать задержки данных.
Отказ от ответственности
Пример предназначен для обучения. Перед использованием на реальном счёте протестируйте стратегию на исторических и потоковых данных с учётом комиссий и задержек.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MetaTrader GoldWarrior02b expert advisor adapted for StockSharp.
/// Combines CCI, an impulse gauge and a ZigZag swing detector to trade near the end of 15 minute blocks.
/// </summary>
public class GoldWarrior02bStrategy : Strategy
{
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<int> _impulsePeriod;
private readonly StrategyParam<int> _zigZagDepth;
private readonly StrategyParam<decimal> _zigZagDeviation;
private readonly StrategyParam<int> _zigZagBackstep;
private readonly StrategyParam<decimal> _profitTarget;
private readonly StrategyParam<decimal> _impulseSellThreshold;
private readonly StrategyParam<decimal> _impulseBuyThreshold;
private readonly StrategyParam<DataType> _candleType;
private CommodityChannelIndex _cci = null!;
private ImpulseIndicator _impulse = null!;
private decimal? _lastZigZag;
private decimal? _previousZigZag;
private int _searchDirection;
private decimal? _currentExtreme;
private int _barsSinceExtreme;
private decimal _previousCci;
private decimal _previousImpulse;
private bool _hasPreviousCci;
private bool _hasPreviousImpulse;
private DateTimeOffset _lastTradeTime;
private decimal _entryPrice;
private decimal _trailingStopPrice;
private bool _trailingActive;
private decimal _maxPriceSinceEntry;
private decimal _minPriceSinceEntry;
/// <summary>
/// Base trading volume.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance in points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Additional offset before activating the trailing stop.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Period used both for CCI and impulse calculations.
/// </summary>
public int ImpulsePeriod
{
get => _impulsePeriod.Value;
set => _impulsePeriod.Value = value;
}
/// <summary>
/// Minimum bars between ZigZag turning points.
/// </summary>
public int ZigZagDepth
{
get => _zigZagDepth.Value;
set => _zigZagDepth.Value = value;
}
/// <summary>
/// Minimum price deviation to confirm a new ZigZag swing.
/// </summary>
public decimal ZigZagDeviation
{
get => _zigZagDeviation.Value;
set => _zigZagDeviation.Value = value;
}
/// <summary>
/// Minimum number of bars before accepting a new swing.
/// </summary>
public int ZigZagBackstep
{
get => _zigZagBackstep.Value;
set => _zigZagBackstep.Value = value;
}
/// <summary>
/// Profit target that forces an early exit from open positions.
/// </summary>
public decimal ProfitTarget
{
get => _profitTarget.Value;
set => _profitTarget.Value = value;
}
/// <summary>
/// Threshold applied to the impulse gauge before opening shorts.
/// </summary>
public decimal ImpulseSellThreshold
{
get => _impulseSellThreshold.Value;
set => _impulseSellThreshold.Value = value;
}
/// <summary>
/// Threshold applied to the impulse gauge before opening longs.
/// </summary>
public decimal ImpulseBuyThreshold
{
get => _impulseBuyThreshold.Value;
set => _impulseBuyThreshold.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public GoldWarrior02bStrategy()
{
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Base trade size", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 150m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in points", "Risk");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 5m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance in points", "Risk");
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step", "Extra distance before trailing activates", "Risk");
_impulsePeriod = Param(nameof(ImpulsePeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Impulse Period", "Period for CCI and impulse averages", "Indicators");
_zigZagDepth = Param(nameof(ZigZagDepth), 12)
.SetGreaterThanZero()
.SetDisplay("ZigZag Depth", "Minimum bars between swings", "Indicators");
_zigZagDeviation = Param(nameof(ZigZagDeviation), 5m)
.SetGreaterThanZero()
.SetDisplay("ZigZag Deviation", "Required price move in points", "Indicators");
_zigZagBackstep = Param(nameof(ZigZagBackstep), 3)
.SetGreaterThanZero()
.SetDisplay("ZigZag Backstep", "Bars before confirming a new swing", "Indicators");
_profitTarget = Param(nameof(ProfitTarget), 300m)
.SetNotNegative()
.SetDisplay("Profit Target", "Close all profit in account currency", "Risk");
_impulseSellThreshold = Param(nameof(ImpulseSellThreshold), -30m)
.SetDisplay("Impulse Sell", "Impulse threshold for shorts", "Indicators");
_impulseBuyThreshold = Param(nameof(ImpulseBuyThreshold), 30m)
.SetDisplay("Impulse Buy", "Impulse threshold for longs", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cci?.Reset();
_impulse?.Reset();
_lastZigZag = null;
_previousZigZag = null;
_searchDirection = 1;
_currentExtreme = null;
_barsSinceExtreme = 0;
_previousCci = 0m;
_previousImpulse = 0m;
_hasPreviousCci = false;
_hasPreviousImpulse = false;
_lastTradeTime = DateTimeOffset.MinValue;
ResetPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = BaseVolume;
_cci = new CommodityChannelIndex { Length = ImpulsePeriod };
_impulse = new ImpulseIndicator
{
Length = ImpulsePeriod,
PriceStep = GetPriceStep()
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, _impulse, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _cci);
DrawIndicator(area, _impulse);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue, decimal impulseValue)
{
if (candle.State != CandleStates.Finished)
return;
_impulse.PriceStep = GetPriceStep();
if (!_cci.IsFormed || !_impulse.IsFormed)
{
_previousCci = cciValue;
_previousImpulse = impulseValue;
_hasPreviousCci = true;
_hasPreviousImpulse = true;
UpdateZigZag(candle);
return;
}
UpdateZigZag(candle);
var hasZigZag = _lastZigZag.HasValue && _previousZigZag.HasValue;
var zigZagUp = hasZigZag && _lastZigZag.Value > _previousZigZag.Value;
var zigZagDown = hasZigZag && _lastZigZag.Value < _previousZigZag.Value;
if (!_hasPreviousCci || !_hasPreviousImpulse)
{
_previousCci = cciValue;
_previousImpulse = impulseValue;
_hasPreviousCci = true;
_hasPreviousImpulse = true;
return;
}
var now = candle.CloseTime;
if ((now - _lastTradeTime).TotalSeconds < 15)
{
_previousCci = cciValue;
_previousImpulse = impulseValue;
return;
}
var sellCondition1 = cciValue < _previousCci && _previousCci > 20m && impulseValue < 0m;
var sellCondition2 = cciValue > 100m && _previousCci > cciValue;
var buyCondition1 = cciValue > _previousCci && _previousCci < -20m && impulseValue > 0m;
var buyCondition2 = cciValue < -100m && _previousCci < cciValue;
var sellSignal = hasZigZag && zigZagUp && (sellCondition1 || sellCondition2);
var buySignal = hasZigZag && zigZagDown && (buyCondition1 || buyCondition2);
if (!hasZigZag || Position != 0)
{
sellSignal = false;
buySignal = false;
}
if (Position == 0 && AllowEntryTime(now))
{
if (sellSignal)
OpenShort(candle, BaseVolume);
else if (buySignal)
OpenLong(candle, BaseVolume);
}
if (Position != 0)
{
HandleActivePosition(candle, now);
}
_previousCci = cciValue;
_previousImpulse = impulseValue;
}
private void HandleActivePosition(ICandleMessage candle, DateTimeOffset now)
{
var step = GetPriceStep();
var stepPrice = GetStepPrice(step);
var stopLossDistance = StopLossPoints * step;
var takeProfitDistance = TakeProfitPoints * step;
var trailingStopDistance = TrailingStopPoints * step;
var trailingStepDistance = TrailingStepPoints * step;
if (Position > 0)
{
_maxPriceSinceEntry = Math.Max(_maxPriceSinceEntry, candle.HighPrice);
if (stopLossDistance > 0m && candle.LowPrice <= _entryPrice - stopLossDistance)
{
SellMarket(Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
if (takeProfitDistance > 0m && candle.HighPrice >= _entryPrice + takeProfitDistance)
{
SellMarket(Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
if (trailingStopDistance > 0m)
{
var move = candle.ClosePrice - _entryPrice;
if (move >= trailingStopDistance + trailingStepDistance)
{
var newTrail = candle.ClosePrice - trailingStopDistance;
if (!_trailingActive || newTrail > _trailingStopPrice)
{
_trailingStopPrice = newTrail;
_trailingActive = true;
}
}
if (_trailingActive && candle.LowPrice <= _trailingStopPrice)
{
SellMarket(Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
}
}
else if (Position < 0)
{
_minPriceSinceEntry = Math.Min(_minPriceSinceEntry, candle.LowPrice);
if (stopLossDistance > 0m && candle.HighPrice >= _entryPrice + stopLossDistance)
{
BuyMarket(-Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
if (takeProfitDistance > 0m && candle.LowPrice <= _entryPrice - takeProfitDistance)
{
BuyMarket(-Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
if (trailingStopDistance > 0m)
{
var move = _entryPrice - candle.ClosePrice;
if (move >= trailingStopDistance + trailingStepDistance)
{
var newTrail = candle.ClosePrice + trailingStopDistance;
if (!_trailingActive || newTrail < _trailingStopPrice)
{
_trailingStopPrice = newTrail;
_trailingActive = true;
}
}
if (_trailingActive && candle.HighPrice >= _trailingStopPrice)
{
BuyMarket(-Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
}
}
var currentPnL = CalculateOpenPnL(candle.ClosePrice, step, stepPrice);
if (ProfitTarget > 0m && currentPnL >= ProfitTarget)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_lastTradeTime = now;
ResetPositionState();
return;
}
}
private decimal CalculateOpenPnL(decimal closePrice, decimal step, decimal stepPrice)
{
if (Position == 0)
return 0m;
if (step <= 0m)
step = 1m;
if (stepPrice <= 0m)
stepPrice = step;
if (Position > 0)
{
var diff = closePrice - _entryPrice;
return diff / step * stepPrice * Position;
}
else
{
var diff = _entryPrice - closePrice;
return diff / step * stepPrice * -Position;
}
}
private void OpenLong(ICandleMessage candle, decimal volume)
{
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_maxPriceSinceEntry = candle.ClosePrice;
_minPriceSinceEntry = candle.ClosePrice;
_trailingActive = false;
_trailingStopPrice = 0m;
_lastTradeTime = candle.CloseTime;
}
private void OpenShort(ICandleMessage candle, decimal volume)
{
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_maxPriceSinceEntry = candle.ClosePrice;
_minPriceSinceEntry = candle.ClosePrice;
_trailingActive = false;
_trailingStopPrice = 0m;
_lastTradeTime = candle.CloseTime;
}
private void ResetPositionState()
{
_entryPrice = 0m;
_maxPriceSinceEntry = 0m;
_minPriceSinceEntry = 0m;
_trailingActive = false;
_trailingStopPrice = 0m;
}
private bool AllowEntryTime(DateTimeOffset time)
{
return true;
}
private void UpdateZigZag(ICandleMessage candle)
{
var step = GetPriceStep();
var deviation = ZigZagDeviation * step;
var minBars = Math.Max(1, Math.Max(ZigZagDepth, ZigZagBackstep));
if (_currentExtreme is null)
{
_currentExtreme = _searchDirection > 0 ? candle.HighPrice : candle.LowPrice;
_barsSinceExtreme = 0;
return;
}
if (_searchDirection > 0)
{
if (candle.HighPrice > _currentExtreme.Value)
{
_currentExtreme = candle.HighPrice;
_barsSinceExtreme = 0;
}
else
{
_barsSinceExtreme++;
}
var drop = _currentExtreme.Value - candle.LowPrice;
if (drop >= deviation && _barsSinceExtreme >= minBars)
{
_previousZigZag = _lastZigZag;
_lastZigZag = _currentExtreme;
_searchDirection = -1;
_currentExtreme = candle.LowPrice;
_barsSinceExtreme = 0;
}
}
else
{
if (candle.LowPrice < _currentExtreme.Value)
{
_currentExtreme = candle.LowPrice;
_barsSinceExtreme = 0;
}
else
{
_barsSinceExtreme++;
}
var rise = candle.HighPrice - _currentExtreme.Value;
if (rise >= deviation && _barsSinceExtreme >= minBars)
{
_previousZigZag = _lastZigZag;
_lastZigZag = _currentExtreme;
_searchDirection = 1;
_currentExtreme = candle.HighPrice;
_barsSinceExtreme = 0;
}
}
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep ?? 1m;
return step > 0m ? step : 1m;
}
private decimal GetStepPrice(decimal step)
{
var stepPrice = step;
return stepPrice > 0m ? stepPrice : step;
}
private sealed class ImpulseIndicator : BaseIndicator
{
public int Length { get; set; } = 21;
public decimal PriceStep { get; set; } = 1m;
private readonly Queue<decimal> _buffer = new();
private decimal _sum;
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<ICandleMessage>();
var step = PriceStep > 0m ? PriceStep : 1m;
var value = (candle.OpenPrice - candle.ClosePrice) / step;
_buffer.Enqueue(value);
_sum += value;
if (_buffer.Count > Length)
_sum -= _buffer.Dequeue();
if (_buffer.Count < Length)
{
IsFormed = false;
return new DecimalIndicatorValue(this, 0m, input.Time);
}
IsFormed = true;
var average = _sum / Length;
return new DecimalIndicatorValue(this, average, input.Time);
}
public override void Reset()
{
base.Reset();
_buffer.Clear();
_sum = 0m;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import CommodityChannelIndex, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
from indicator_extensions import *
class gold_warrior02b_strategy(Strategy):
def __init__(self):
super(gold_warrior02b_strategy, self).__init__()
self._base_volume = self.Param("BaseVolume", 0.1)
self._stop_loss_points = self.Param("StopLossPoints", 100.0)
self._take_profit_points = self.Param("TakeProfitPoints", 150.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 5.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0)
self._impulse_period = self.Param("ImpulsePeriod", 21)
self._zig_zag_depth = self.Param("ZigZagDepth", 12)
self._zig_zag_deviation = self.Param("ZigZagDeviation", 5.0)
self._zig_zag_backstep = self.Param("ZigZagBackstep", 3)
self._profit_target = self.Param("ProfitTarget", 300.0)
self._impulse_sell_threshold = self.Param("ImpulseSellThreshold", -30.0)
self._impulse_buy_threshold = self.Param("ImpulseBuyThreshold", 30.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._cci = None
# Impulse indicator state (inline SMA of (open-close)/step)
self._impulse_buffer = []
self._impulse_sum = 0.0
self._impulse_formed = False
# ZigZag state
self._last_zigzag = None
self._previous_zigzag = None
self._search_direction = 1
self._current_extreme = None
self._bars_since_extreme = 0
# Previous indicator values
self._previous_cci = 0.0
self._previous_impulse = 0.0
self._has_previous_cci = False
self._has_previous_impulse = False
# Position management
self._last_trade_time = None
self._entry_price = 0.0
self._trailing_stop_price = 0.0
self._trailing_active = False
self._max_price_since_entry = 0.0
self._min_price_since_entry = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(gold_warrior02b_strategy, self).OnStarted2(time)
self._cci = CommodityChannelIndex()
self._cci.Length = self._impulse_period.Value
self._impulse_buffer = []
self._impulse_sum = 0.0
self._impulse_formed = False
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
return step if step > 0 else 1.0
def _compute_impulse(self, candle):
step = self._get_price_step()
value = (float(candle.OpenPrice) - float(candle.ClosePrice)) / step
self._impulse_buffer.append(value)
self._impulse_sum += value
length = self._impulse_period.Value
if len(self._impulse_buffer) > length:
self._impulse_sum -= self._impulse_buffer.pop(0)
if len(self._impulse_buffer) < length:
self._impulse_formed = False
return 0.0
self._impulse_formed = True
return self._impulse_sum / length
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._cci, candle)
civ.IsFinal = True
cci_result = self._cci.Process(civ)
cci_value = float(cci_result.Value) if not cci_result.IsEmpty else 0.0
impulse_value = self._compute_impulse(candle)
if not self._cci.IsFormed or not self._impulse_formed:
self._previous_cci = cci_value
self._previous_impulse = impulse_value
self._has_previous_cci = True
self._has_previous_impulse = True
self._update_zigzag(candle)
return
self._update_zigzag(candle)
has_zigzag = self._last_zigzag is not None and self._previous_zigzag is not None
zigzag_up = has_zigzag and self._last_zigzag > self._previous_zigzag
zigzag_down = has_zigzag and self._last_zigzag < self._previous_zigzag
if not self._has_previous_cci or not self._has_previous_impulse:
self._previous_cci = cci_value
self._previous_impulse = impulse_value
self._has_previous_cci = True
self._has_previous_impulse = True
return
now = candle.CloseTime
if self._last_trade_time is not None and (now - self._last_trade_time).TotalSeconds < 15:
self._previous_cci = cci_value
self._previous_impulse = impulse_value
return
sell_condition1 = cci_value < self._previous_cci and self._previous_cci > 20.0 and impulse_value < 0.0
sell_condition2 = cci_value > 100.0 and self._previous_cci > cci_value
buy_condition1 = cci_value > self._previous_cci and self._previous_cci < -20.0 and impulse_value > 0.0
buy_condition2 = cci_value < -100.0 and self._previous_cci < cci_value
sell_signal = has_zigzag and zigzag_up and (sell_condition1 or sell_condition2)
buy_signal = has_zigzag and zigzag_down and (buy_condition1 or buy_condition2)
if not has_zigzag or self.Position != 0:
sell_signal = False
buy_signal = False
if self.Position == 0:
if sell_signal:
self._open_short(candle)
elif buy_signal:
self._open_long(candle)
if self.Position != 0:
self._handle_active_position(candle, now)
self._previous_cci = cci_value
self._previous_impulse = impulse_value
def _handle_active_position(self, candle, now):
step = self._get_price_step()
sl_dist = self._stop_loss_points.Value * step
tp_dist = self._take_profit_points.Value * step
trail_dist = self._trailing_stop_points.Value * step
trail_step_dist = self._trailing_step_points.Value * step
if self.Position > 0:
self._max_price_since_entry = max(self._max_price_since_entry, float(candle.HighPrice))
if sl_dist > 0 and float(candle.LowPrice) <= self._entry_price - sl_dist:
self.SellMarket(self.Position)
self._last_trade_time = now
self._reset_position_state()
return
if tp_dist > 0 and float(candle.HighPrice) >= self._entry_price + tp_dist:
self.SellMarket(self.Position)
self._last_trade_time = now
self._reset_position_state()
return
if trail_dist > 0:
move = float(candle.ClosePrice) - self._entry_price
if move >= trail_dist + trail_step_dist:
new_trail = float(candle.ClosePrice) - trail_dist
if not self._trailing_active or new_trail > self._trailing_stop_price:
self._trailing_stop_price = new_trail
self._trailing_active = True
if self._trailing_active and float(candle.LowPrice) <= self._trailing_stop_price:
self.SellMarket(self.Position)
self._last_trade_time = now
self._reset_position_state()
return
elif self.Position < 0:
self._min_price_since_entry = min(self._min_price_since_entry, float(candle.LowPrice))
if sl_dist > 0 and float(candle.HighPrice) >= self._entry_price + sl_dist:
self.BuyMarket(abs(self.Position))
self._last_trade_time = now
self._reset_position_state()
return
if tp_dist > 0 and float(candle.LowPrice) <= self._entry_price - tp_dist:
self.BuyMarket(abs(self.Position))
self._last_trade_time = now
self._reset_position_state()
return
if trail_dist > 0:
move = self._entry_price - float(candle.ClosePrice)
if move >= trail_dist + trail_step_dist:
new_trail = float(candle.ClosePrice) + trail_dist
if not self._trailing_active or new_trail < self._trailing_stop_price:
self._trailing_stop_price = new_trail
self._trailing_active = True
if self._trailing_active and float(candle.HighPrice) >= self._trailing_stop_price:
self.BuyMarket(abs(self.Position))
self._last_trade_time = now
self._reset_position_state()
return
current_pnl = self._calculate_open_pnl(float(candle.ClosePrice), step)
if self._profit_target.Value > 0 and current_pnl >= self._profit_target.Value:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._last_trade_time = now
self._reset_position_state()
def _calculate_open_pnl(self, close_price, step):
if self.Position == 0:
return 0.0
if step <= 0:
step = 1.0
if self.Position > 0:
diff = close_price - self._entry_price
return diff / step * step * self.Position
else:
diff = self._entry_price - close_price
return diff / step * step * abs(self.Position)
def _open_long(self, candle):
self.BuyMarket(self._base_volume.Value)
self._entry_price = float(candle.ClosePrice)
self._max_price_since_entry = float(candle.ClosePrice)
self._min_price_since_entry = float(candle.ClosePrice)
self._trailing_active = False
self._trailing_stop_price = 0.0
self._last_trade_time = candle.CloseTime
def _open_short(self, candle):
self.SellMarket(self._base_volume.Value)
self._entry_price = float(candle.ClosePrice)
self._max_price_since_entry = float(candle.ClosePrice)
self._min_price_since_entry = float(candle.ClosePrice)
self._trailing_active = False
self._trailing_stop_price = 0.0
self._last_trade_time = candle.CloseTime
def _reset_position_state(self):
self._entry_price = 0.0
self._max_price_since_entry = 0.0
self._min_price_since_entry = 0.0
self._trailing_active = False
self._trailing_stop_price = 0.0
def _update_zigzag(self, candle):
step = self._get_price_step()
deviation = self._zig_zag_deviation.Value * step
min_bars = max(1, max(self._zig_zag_depth.Value, self._zig_zag_backstep.Value))
if self._current_extreme is None:
self._current_extreme = float(candle.HighPrice) if self._search_direction > 0 else float(candle.LowPrice)
self._bars_since_extreme = 0
return
if self._search_direction > 0:
if float(candle.HighPrice) > self._current_extreme:
self._current_extreme = float(candle.HighPrice)
self._bars_since_extreme = 0
else:
self._bars_since_extreme += 1
drop = self._current_extreme - float(candle.LowPrice)
if drop >= deviation and self._bars_since_extreme >= min_bars:
self._previous_zigzag = self._last_zigzag
self._last_zigzag = self._current_extreme
self._search_direction = -1
self._current_extreme = float(candle.LowPrice)
self._bars_since_extreme = 0
else:
if float(candle.LowPrice) < self._current_extreme:
self._current_extreme = float(candle.LowPrice)
self._bars_since_extreme = 0
else:
self._bars_since_extreme += 1
rise = float(candle.HighPrice) - self._current_extreme
if rise >= deviation and self._bars_since_extreme >= min_bars:
self._previous_zigzag = self._last_zigzag
self._last_zigzag = self._current_extreme
self._search_direction = 1
self._current_extreme = float(candle.HighPrice)
self._bars_since_extreme = 0
def OnReseted(self):
super(gold_warrior02b_strategy, self).OnReseted()
self._cci = None
self._impulse_buffer = []
self._impulse_sum = 0.0
self._impulse_formed = False
self._last_zigzag = None
self._previous_zigzag = None
self._search_direction = 1
self._current_extreme = None
self._bars_since_extreme = 0
self._previous_cci = 0.0
self._previous_impulse = 0.0
self._has_previous_cci = False
self._has_previous_impulse = False
self._last_trade_time = None
self._reset_position_state()
def CreateClone(self):
return gold_warrior02b_strategy()