Открыть на GitHub

Rock Trader Neuro Strategy

Стратегия торгует с помощью полос Боллинджера и простого нейрона. Ширина полос за последние семь свечей нормализуется в диапазон [-1,1] и умножается на фиксированные веса. Взвешенная сумма проходит через активацию гиперболическим тангенсом. Отрицательный выход открывает длинную позицию, положительный — короткую. Позиции закрываются по стоп-лоссу или тейк-профиту.

Детали

  • Условия входа:
    • Лонг: выход нейрона < 0
    • Шорт: выход нейрона > 0
  • Длинные/Короткие: обе стороны
  • Условия выхода:
    • Достижение стоп-лосса или тейк-профита
  • Стопы: абсолютное расстояние по цене
  • Значения по умолчанию:
    • StopLoss = 30
    • TakeProfit = 100
    • Lot = 1
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
  • Фильтры:
    • Категория: Neural
    • Направление: обе стороны
    • Индикаторы: Bollinger Bands
    • Стопы: да
    • Сложность: средняя
    • Таймфрейм: краткосрочный
    • Сезонность: нет
    • Нейросети: да
    • Дивергенция: нет
    • Уровень риска: средний
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>
/// Strategy using Bollinger Bands and a simple neuron to generate signals.
/// </summary>
public class RockTraderNeuroStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _lot;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _band1;
	private decimal _band2;
	private decimal _band3;
	private decimal _band4;
	private decimal _band5;
	private decimal _band6;
	private decimal _band7;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _takePrice;

	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public decimal Lot
	{
		get => _lot.Value;
		set => _lot.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public RockTraderNeuroStrategy()
	{
		_stopLoss = Param(nameof(StopLoss), 30m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 100m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_lot = Param(nameof(Lot), 1m)
			.SetDisplay("Lot", "Lots to trade", "Trading");

		_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();

		_band1 = _band2 = _band3 = _band4 = _band5 = _band6 = _band7 = 0m;
		_entryPrice = 0m;
		_stopPrice = 0m;
		_takePrice = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = Lot;
		_band1 = _band2 = _band3 = _band4 = _band5 = _band6 = _band7 = 0m;
		_entryPrice = 0m;
		_stopPrice = 0m;
		_takePrice = 0m;

		var bb = new BollingerBands
		{
			Length = 20,
			Width = 2m
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bb, ProcessCandleEx)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandleEx(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower ||
			bb.MovingAverage is not decimal middle)
			return;

		// Calculate current Bollinger Band width
		var bandWidth = middle == 0m ? 0m : (upper - lower) / middle;

		// Shift previous values to keep last seven widths
		_band7 = _band6;
		_band6 = _band5;
		_band5 = _band4;
		_band4 = _band3;
		_band3 = _band2;
		_band2 = _band1;
		_band1 = bandWidth;

		// Wait until all values are filled
		if (_band7 == 0m)
			return;

		var min = Math.Min(Math.Min(Math.Min(_band1, _band2), Math.Min(_band3, _band4)), Math.Min(_band5, Math.Min(_band6, _band7)));
		var max = Math.Max(Math.Max(Math.Max(_band1, _band2), Math.Max(_band3, _band4)), Math.Max(_band5, Math.Max(_band6, _band7)));

		if (max == min)
			return;

		const decimal d1 = -1m;
		const decimal d2 = 1m;

		decimal Normalize(decimal x) => (x - min) * (d2 - d1) / (max - min) + d1;

		var n1 = Normalize(_band1);
		var n2 = Normalize(_band2);
		var n3 = Normalize(_band3);
		var n4 = Normalize(_band4);
		var n5 = Normalize(_band5);
		var n6 = Normalize(_band6);
		var n7 = Normalize(_band7);

		// Weighted sum of inputs
		var net = n1 * 0.8m + n2 * -0.9m + n3 * 0.7m + n4 * 0.9m + n5 * -1m + n6 * 0.5m + n7 * 0m;

		// Activation using hyperbolic tangent
		var output = Tanh(net * 2m);

		if (Position > 0)
		{
			if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
				SellMarket();
		}
		else if (Position < 0)
		{
			if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
				BuyMarket();
		}
		else
		{
			if (output < -0.5m)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _entryPrice - StopLoss;
				_takePrice = _entryPrice + TakeProfit;
			}
			else if (output > 0.5m)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _entryPrice + StopLoss;
				_takePrice = _entryPrice - TakeProfit;
			}
		}
	}

	private static decimal Tanh(decimal x)
	{
		var d = Math.Clamp((double)x, -20d, 20d);
		var ePos = Math.Exp(d);
		var eNeg = Math.Exp(-d);
		return (decimal)((ePos - eNeg) / (ePos + eNeg));
	}
}