Открыть на GitHub

Стратегия JB

Описание

JB — это стратегия, перенесённая из советника fxDreema, которая сочетает фильтры тренда, подтверждение импульса и пробой волатильности:

  • Фильтр тренда. Предыдущая свеча должна закрыться выше (для покупок) либо ниже (для продаж) 100-периодной простой скользящей средней.
  • Фильтр импульса. Подтверждение направления осуществляется индикатором Force Index с периодом 100 (положительное значение — лонг, отрицательное — шорт).
  • Триггер волатильности. Вход происходит, когда предыдущая свеча закрывается за пределами соответствующей полосы Боллинджера (период 20, отклонение 2.0).
  • Управление позицией. Объём сделки увеличивается по принципу мартингейла после убыточного цикла и возвращается к базовому уровню после прибыльного цикла.
  • Выход. Все позиции закрываются, когда средняя нереализованная прибыль на контракт достигает заданного порога.

Параметры

Имя Описание
SmaPeriod Период SMA для фильтра тренда (по умолчанию 100).
ForcePeriod Период индикатора Force Index (по умолчанию 100).
BollingerPeriod Период полос Боллинджера (по умолчанию 20).
BollingerDeviation Множитель стандартного отклонения полос Боллинджера (по умолчанию 2.0).
BaseVolume Базовый объём заявки до применения мартингейла (по умолчанию 0.1).
LossMultiplier Множитель объёма после убыточного цикла (по умолчанию 1.55).
AverageProfitTarget Целевой уровень средней нереализованной прибыли на контракт для закрытия позиций (по умолчанию 2.8).
CandleType Тип свечей, используемых для расчётов (по умолчанию минутные).

Сигналы

Покупка

  1. Предыдущая свеча закрылась на уровне нижней полосы Боллинджера или ниже.
  2. Предыдущая цена закрытия выше 100-периодной SMA.
  3. Force Index положителен.

Продажа

  1. Предыдущая свеча закрылась на уровне верхней полосы Боллинджера или выше.
  2. Предыдущая цена закрытия ниже 100-периодной SMA.
  3. Force Index отрицателен.

Выходы

  • При достижении параметра AverageProfitTarget по средней нереализованной прибыли на контракт стратегия закрывает всю позицию.
  • После возврата позиции к нулю объём следующей сделки увеличивается по множителю LossMultiplier, если последний цикл убыточен, и сбрасывается к BaseVolume, если цикл прибыльный.

Примечания

  • Мартингейловое управление объёмом основано на реализованном PnL, поэтому используйте стратегию только на инструментах, где допустимо наращивание объёма.
  • В StockSharp используется нетто-позиция, поэтому возможные встречные сделки из оригинального MQL решены через агрегированную позицию.
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;

public class JbStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public JbStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}