Стратегия Neural Network ATR
Обзор
Стратегия переносит логику советника "Neurotest" в экосистему StockSharp, сочетая легковесный нейронный слой и управление рисками на базе ATR. Каждая завершённая свеча M15 (параметр настраивается) преобразуется в пять нормализованных признаков: импульс закрытия, внутридневной диапазон, размер тела, рост объёма и относительную волатильность (отношение ATR к цене). Одно скрытое звено с сигмоидальным выходом генерирует вероятностный сигнал, который масштабируется адаптивной скоростью обучения и сравнивается с порогами покупок и продаж.
Правила торговли
- Подписаться на свечи выбранного таймфрейма и вычислить ATR того же периода.
- Построить пять нормализованных признаков по предыдущей и текущей завершённой свече, затем вычислить отклик нейронной сети.
- Если скорректированный прогноз выше порога покупок и позиция не длинная — открыть длинную позицию (или закрыть короткую и перевернуться).
- Если прогноз ниже порога продаж и позиция не короткая — открыть короткую позицию.
- После входа прикрепить стоп-лосс и тейк-профит, рассчитывая расстояние по ATR. При отсутствии значений ATR использовать резервное расстояние в пунктах.
- Если текущий спред превышает заданный лимит, свеча игнорируется.
Управление рисками
- Объём позиции рассчитывается так, чтобы убыток по стоп-лоссу равнялся
Max Risk %от капитала счёта. - Защитные заявки используют настраиваемое соотношение риск/прибыль.
- Торговля автоматически приостанавливается при превышении дневной или общей просадки.
- Пенальти снижает скорость обучения на 10% (до минимального уровня), если дневная цель по прибыли не достигнута. На следующем торговом дне штраф сбрасывается.
Параметры
| Параметр | Описание |
|---|---|
| Max Risk % | Максимальный риск на сделку в процентах от капитала. |
| Daily Loss % | Допустимая дневная просадка. |
| Total Loss % | Максимальная совокупная просадка. |
| Daily Profit % | Цель по дневной прибыли для отмены штрафа. |
| Learning Rate | Масштабирование выходного сигнала нейросети. |
| Hidden Layer | Количество нейронов скрытого слоя. |
| Buy Threshold / Sell Threshold | Пороги открытия длинных и коротких позиций. |
| Candle Type | Тип и таймфрейм свечей для расчётов. |
| ATR Period | Период индикатора ATR. |
| Max Spread | Максимально допустимый спред в шагах цены. |
| Risk Reward | Множитель тейк-профита относительно стопа. |
| Fallback Stop | Стоп-лосс в пунктах при отсутствии ATR. |
Примечания
- Требуется подписка Level1, чтобы контролировать bid/ask спред перед каждым решением.
- Веса нейросети инициализируются детерминированно (seed 42), а динамика скорости обучения имитирует адаптивное поведение исходного советника.
- Для корректного расчёта объёма убедитесь, что портфель предоставляет
CurrentValue,StepPriceи ограничения по объёму.
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>
/// Adaptive neural network strategy with ATR based risk control.
/// Combines normalized candle features with a lightweight neural layer
/// to generate probabilistic long and short signals.
/// Applies daily and total drawdown checks together with ATR driven
/// position sizing and protective orders.
/// </summary>
public class NeuralNetworkAtrStrategy : Strategy
{
private readonly StrategyParam<decimal> _maxRiskPerTrade;
private readonly StrategyParam<decimal> _dailyLossLimit;
private readonly StrategyParam<decimal> _totalLossLimit;
private readonly StrategyParam<decimal> _dailyProfitTarget;
private readonly StrategyParam<decimal> _initialLearningRate;
private readonly StrategyParam<int> _hiddenLayerSize;
private readonly StrategyParam<decimal> _buyThreshold;
private readonly StrategyParam<decimal> _sellThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private readonly StrategyParam<decimal> _riskRewardRatio;
private readonly StrategyParam<int> _fallbackStopLossPoints;
private readonly StrategyParam<int> _inputSize;
private readonly StrategyParam<decimal> _minimumLearningRate;
private readonly StrategyParam<decimal> _featureClamp;
private static readonly object _sync = new();
private decimal _accountEquityAtStart;
private decimal _dailyEquityAtStart;
private DateTime _lastTradeDay;
private DateTime _lastPenaltyDay;
private bool _tradingHalted;
private decimal _learningRate;
private ATR _atrIndicator;
private decimal[] _weightsInputHidden = Array.Empty<decimal>();
private decimal[] _biasHidden = Array.Empty<decimal>();
private decimal[] _weightsHiddenOutput = Array.Empty<decimal>();
private decimal[] _hiddenOutputs = Array.Empty<decimal>();
private decimal _biasOutput;
private ICandleMessage _previousCandle;
private decimal _bestBidPrice;
private decimal _bestAskPrice;
private bool _hasBestBid;
private bool _hasBestAsk;
/// <summary>
/// Maximum share of equity risked in a single trade (percentage).
/// </summary>
public decimal MaxRiskPerTrade
{
get => _maxRiskPerTrade.Value;
set => _maxRiskPerTrade.Value = value;
}
/// <summary>
/// Daily drawdown threshold in percent.
/// </summary>
public decimal DailyLossLimit
{
get => _dailyLossLimit.Value;
set => _dailyLossLimit.Value = value;
}
/// <summary>
/// Total drawdown threshold in percent.
/// </summary>
public decimal TotalLossLimit
{
get => _totalLossLimit.Value;
set => _totalLossLimit.Value = value;
}
/// <summary>
/// Minimum daily profit target before penalty is removed.
/// </summary>
public decimal DailyProfitTarget
{
get => _dailyProfitTarget.Value;
set => _dailyProfitTarget.Value = value;
}
/// <summary>
/// Initial learning rate that scales signal intensity.
/// </summary>
public decimal InitialLearningRate
{
get => _initialLearningRate.Value;
set => _initialLearningRate.Value = value;
}
/// <summary>
/// Number of neurons in the hidden layer.
/// </summary>
public int HiddenLayerSize
{
get => _hiddenLayerSize.Value;
set => _hiddenLayerSize.Value = value;
}
/// <summary>
/// Threshold for opening long positions.
/// </summary>
public decimal BuyThreshold
{
get => _buyThreshold.Value;
set => _buyThreshold.Value = value;
}
/// <summary>
/// Threshold for opening short positions.
/// </summary>
public decimal SellThreshold
{
get => _sellThreshold.Value;
set => _sellThreshold.Value = value;
}
/// <summary>
/// Selected candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// ATR period used to measure volatility.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Maximum acceptable spread in price steps.
/// </summary>
public decimal MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Risk to reward ratio for protective orders.
/// </summary>
public decimal RiskRewardRatio
{
get => _riskRewardRatio.Value;
set => _riskRewardRatio.Value = value;
}
/// <summary>
/// Fallback stop-loss distance in price steps when ATR is unavailable.
/// </summary>
public int FallbackStopLossPoints
{
get => _fallbackStopLossPoints.Value;
set => _fallbackStopLossPoints.Value = value;
}
/// <summary>
/// Number of input features processed by the neural layer.
/// </summary>
public int InputSize
{
get => _inputSize.Value;
set => _inputSize.Value = value;
}
/// <summary>
/// Minimum learning rate applied when adapting the network weights.
/// </summary>
public decimal MinimumLearningRate
{
get => _minimumLearningRate.Value;
set => _minimumLearningRate.Value = value;
}
/// <summary>
/// Absolute value used to clamp normalized features.
/// </summary>
public decimal FeatureClamp
{
get => _featureClamp.Value;
set => _featureClamp.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public NeuralNetworkAtrStrategy()
{
_maxRiskPerTrade = Param(nameof(MaxRiskPerTrade), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Max Risk %", "Maximum risk per trade as percentage of equity", "Risk Management")
.SetOptimize(0.5m, 5.0m, 0.5m);
_dailyLossLimit = Param(nameof(DailyLossLimit), 5.0m)
.SetGreaterThanZero()
.SetDisplay("Daily Loss %", "Maximum permitted daily drawdown", "Risk Management")
.SetOptimize(2.0m, 10.0m, 1.0m);
_totalLossLimit = Param(nameof(TotalLossLimit), 10.0m)
.SetGreaterThanZero()
.SetDisplay("Total Loss %", "Maximum permitted total drawdown", "Risk Management")
.SetOptimize(5.0m, 20.0m, 1.0m);
_dailyProfitTarget = Param(nameof(DailyProfitTarget), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Daily Profit %", "Target daily profit before penalty is avoided", "Risk Management")
.SetOptimize(0.5m, 3.0m, 0.5m);
_initialLearningRate = Param(nameof(InitialLearningRate), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Learning Rate", "Scaling factor for neural output", "Signal")
.SetOptimize(0.005m, 0.05m, 0.005m);
_hiddenLayerSize = Param(nameof(HiddenLayerSize), 5)
.SetGreaterThanZero()
.SetDisplay("Hidden Layer", "Number of neurons in hidden layer", "Signal")
.SetOptimize(3, 9, 2);
_buyThreshold = Param(nameof(BuyThreshold), 0.502m)
.SetDisplay("Buy Threshold", "Prediction level to open long trades", "Signal")
.SetOptimize(0.50m, 0.55m, 0.005m);
_sellThreshold = Param(nameof(SellThreshold), 0.498m)
.SetDisplay("Sell Threshold", "Prediction level to open short trades", "Signal")
.SetOptimize(0.45m, 0.50m, 0.005m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candles used for signal calculations", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of Average True Range indicator", "Signal")
.SetOptimize(10, 30, 2);
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 20m)
.SetGreaterThanZero()
.SetDisplay("Max Spread", "Maximum allowed spread in points", "Execution")
.SetOptimize(5m, 40m, 5m);
_riskRewardRatio = Param(nameof(RiskRewardRatio), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Risk Reward", "Take profit multiple of stop distance", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_fallbackStopLossPoints = Param(nameof(FallbackStopLossPoints), 50)
.SetGreaterThanZero()
.SetDisplay("Fallback Stop", "Stop distance when ATR is not formed", "Risk Management")
.SetOptimize(30, 100, 10);
_inputSize = Param(nameof(InputSize), 5)
.SetGreaterThanZero()
.SetDisplay("Input Size", "Number of features processed by the neural layer", "Neural Network")
.SetOptimize(3, 9, 2);
_minimumLearningRate = Param(nameof(MinimumLearningRate), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Min Learning Rate", "Lower bound applied when adapting learning rate", "Neural Network");
_featureClamp = Param(nameof(FeatureClamp), 1m)
.SetGreaterThanZero()
.SetDisplay("Feature Clamp", "Absolute value used to clamp normalized features", "Neural Network");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_accountEquityAtStart = 0m;
_dailyEquityAtStart = 0m;
_lastTradeDay = default;
_lastPenaltyDay = default;
_tradingHalted = false;
_learningRate = InitialLearningRate;
_previousCandle = null;
_bestBidPrice = 0m;
_bestAskPrice = 0m;
_hasBestBid = false;
_hasBestAsk = false;
_atrIndicator = null;
InitializeNetwork();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_learningRate = InitialLearningRate;
InitializeNetwork();
// Enable protective order handling once at startup.
StartProtection(null, null);
// Prepare ATR indicator and candle subscription.
var atr = new ATR { Length = AtrPeriod };
_atrIndicator = atr;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(candle => ProcessCandle(candle, atr))
.Start();
// Subscribe to level1 updates to evaluate spread in points.
SubscribeLevel1()
.Bind(ProcessLevel1)
.Start();
LogInfo("Neural network strategy started.");
}
private void ProcessLevel1(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
{
_bestBidPrice = bid;
_hasBestBid = true;
}
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
{
_bestAskPrice = ask;
_hasBestAsk = true;
}
}
private void ProcessCandle(ICandleMessage candle, ATR atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
decimal atrValue;
lock (_sync)
{
var atrResult = atr.Process(new CandleIndicatorValue(atr, candle) { IsFinal = true });
if (!atrResult.IsFinal || !atr.IsFormed)
{
_previousCandle = candle;
return;
}
atrValue = atrResult.ToDecimal();
}
if (!_tradingHalted)
{
_tradingHalted = !UpdateEquity(candle.OpenTime);
}
if (_tradingHalted)
return;
if (!EnsureNetworkInitialized())
return;
var spreadPoints = GetSpreadPoints();
if (spreadPoints > 0m && spreadPoints > MaxSpreadPoints)
{
LogInfo($"Spread {spreadPoints:F2} exceeds limit {MaxSpreadPoints}.");
_previousCandle = candle;
return;
}
if (_atrIndicator is { IsFormed: false })
{
_previousCandle = candle;
return;
}
if (_previousCandle is null)
{
_previousCandle = candle;
return;
}
var inputs = BuildInputs(_previousCandle, candle, atrValue);
var prediction = ComputePrediction(inputs);
var adjustedPrediction = AdjustPrediction(prediction);
LogInfo($"Candle {candle.OpenTime:yyyy-MM-dd HH:mm}, Close {candle.ClosePrice}, ATR {atrValue}, Prediction {adjustedPrediction:F4}.");
var volume = CalculateTradeVolume(atrValue);
if (volume <= 0m)
{
_previousCandle = candle;
return;
}
var currentPosition = Position;
if (adjustedPrediction >= BuyThreshold && currentPosition <= 0m)
{
var totalVolume = volume + Math.Abs(currentPosition);
var resultingPosition = currentPosition + totalVolume;
BuyMarket(totalVolume);
AttachProtection(candle.ClosePrice, atrValue, resultingPosition);
LogInfo($"Buy signal. Prediction {adjustedPrediction:F4} above {BuyThreshold}. Volume {totalVolume}.");
}
else if (adjustedPrediction <= SellThreshold && currentPosition >= 0m)
{
var totalVolume = volume + Math.Abs(currentPosition);
var resultingPosition = currentPosition - totalVolume;
SellMarket(totalVolume);
AttachProtection(candle.ClosePrice, atrValue, resultingPosition);
LogInfo($"Sell signal. Prediction {adjustedPrediction:F4} below {SellThreshold}. Volume {totalVolume}.");
}
_previousCandle = candle;
}
private decimal[] BuildInputs(ICandleMessage previous, ICandleMessage current, decimal atrValue)
{
var inputs = new decimal[InputSize];
var previousClose = previous.ClosePrice;
var currentClose = current.ClosePrice;
var priceChange = previousClose != 0m ? (currentClose - previousClose) / previousClose : 0m;
inputs[0] = NormalizeFeature(priceChange);
var range = current.ClosePrice != 0m ? (current.HighPrice - current.LowPrice) / current.ClosePrice : 0m;
inputs[1] = NormalizeFeature(range);
var body = current.HighPrice != current.LowPrice ? (current.ClosePrice - current.OpenPrice) / (current.HighPrice - current.LowPrice) : 0m;
inputs[2] = NormalizeFeature(body);
var previousVolume = previous.TotalVolume;
var currentVolume = current.TotalVolume;
var volumeChange = previousVolume > 0 ? (currentVolume - previousVolume) / previousVolume : 0m;
inputs[3] = NormalizeFeature(volumeChange);
var atrNormalized = current.ClosePrice != 0m ? atrValue / current.ClosePrice : 0m;
inputs[4] = NormalizeFeature(atrNormalized);
return inputs;
}
private decimal NormalizeFeature(decimal value)
{
if (value > FeatureClamp)
value = FeatureClamp;
else if (value < -FeatureClamp)
value = -FeatureClamp;
return (value + FeatureClamp) / (2m * FeatureClamp);
}
private decimal ComputePrediction(IReadOnlyList<decimal> inputs)
{
var hiddenLength = _biasHidden.Length;
for (var j = 0; j < hiddenLength; j++)
{
var activation = _biasHidden[j];
for (var i = 0; i < InputSize; i++)
{
activation += inputs[i] * _weightsInputHidden[i * hiddenLength + j];
}
_hiddenOutputs[j] = activation > 0m ? activation : 0m;
}
var output = _biasOutput;
for (var j = 0; j < hiddenLength; j++)
{
output += _hiddenOutputs[j] * _weightsHiddenOutput[j];
}
return Sigmoid(output);
}
private decimal AdjustPrediction(decimal prediction)
{
var adjusted = prediction * (1m + _learningRate);
if (adjusted > 1m)
adjusted = 1m;
else if (adjusted < 0m)
adjusted = 0m;
return adjusted;
}
private decimal Sigmoid(decimal value)
{
var v = (double)value;
var result = 1.0 / (1.0 + Math.Exp(-v));
return (decimal)result;
}
private decimal CalculateTradeVolume(decimal atrValue)
{
var portfolio = Portfolio;
if (portfolio is null)
return Volume;
var equity = portfolio.CurrentValue ?? 0m;
if (equity <= 0m)
return Volume;
var stopLossPoints = CalculateStopLossPoints(atrValue);
if (stopLossPoints <= 0)
return Volume;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
if (stepPrice <= 0m)
return Volume;
var riskAmount = equity * (MaxRiskPerTrade / 100m);
if (riskAmount <= 0m)
return Volume;
var riskPerContract = stopLossPoints * stepPrice;
if (riskPerContract <= 0m)
return Volume;
var rawVolume = riskAmount / riskPerContract;
var volumeStep = Security?.VolumeStep ?? 0m;
if (volumeStep > 0m)
rawVolume = Math.Floor(rawVolume / volumeStep) * volumeStep;
var minVolume = Security?.MinVolume ?? 0m;
if (minVolume > 0m && rawVolume < minVolume)
rawVolume = minVolume;
var maxVolume = Security?.MaxVolume ?? 0m;
if (maxVolume > 0m && rawVolume > maxVolume)
rawVolume = maxVolume;
return rawVolume > 0m ? rawVolume : Volume;
}
private int CalculateStopLossPoints(decimal atrValue)
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep > 0m && atrValue > 0m)
{
var rawPoints = atrValue / priceStep;
var rounded = (int)Math.Round((double)rawPoints);
if (rounded > 0)
return rounded;
}
return FallbackStopLossPoints;
}
private void AttachProtection(decimal referencePrice, decimal atrValue, decimal resultingPosition)
{
// Protection handled by StartProtection; no manual stop/take-profit setting needed.
}
private bool UpdateEquity(DateTimeOffset time)
{
var portfolio = Portfolio;
if (portfolio is null)
return true;
var equity = portfolio.CurrentValue ?? 0m;
if (equity <= 0m)
return true;
var currentDay = time.Date;
if (_accountEquityAtStart <= 0m)
{
_accountEquityAtStart = equity;
_dailyEquityAtStart = equity;
_lastTradeDay = currentDay;
_lastPenaltyDay = default;
return true;
}
if (_lastTradeDay != currentDay)
{
_dailyEquityAtStart = equity;
_lastTradeDay = currentDay;
_lastPenaltyDay = default;
}
var totalDrawdown = _accountEquityAtStart > 0m ? (_accountEquityAtStart - equity) / _accountEquityAtStart * 100m : 0m;
var dailyDrawdown = _dailyEquityAtStart > 0m ? (_dailyEquityAtStart - equity) / _dailyEquityAtStart * 100m : 0m;
if (dailyDrawdown >= DailyLossLimit || totalDrawdown >= TotalLossLimit)
{
LogInfo($"Drawdown protection activated. Daily {dailyDrawdown:F2}% Total {totalDrawdown:F2}%.");
return false;
}
var dailyProfit = _dailyEquityAtStart > 0m ? (equity - _dailyEquityAtStart) / _dailyEquityAtStart * 100m : 0m;
if (dailyProfit < DailyProfitTarget && _lastPenaltyDay != _lastTradeDay)
{
ApplyPenalty();
_lastPenaltyDay = _lastTradeDay;
}
return true;
}
private void ApplyPenalty()
{
var updated = _learningRate * 0.9m;
if (updated < MinimumLearningRate)
updated = MinimumLearningRate;
if (updated < _learningRate)
{
_learningRate = updated;
LogInfo($"Penalty applied. Learning rate reduced to {_learningRate:F6}.");
}
}
private decimal GetSpreadPoints()
{
if (!_hasBestBid || !_hasBestAsk)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 0m;
var spread = _bestAskPrice - _bestBidPrice;
if (spread <= 0m)
return 0m;
return spread / priceStep;
}
private bool EnsureNetworkInitialized()
{
var hiddenLength = Math.Max(1, HiddenLayerSize);
if (_weightsInputHidden.Length == InputSize * hiddenLength)
return true;
InitializeNetwork();
return _weightsInputHidden.Length == InputSize * hiddenLength;
}
private void InitializeNetwork()
{
var hiddenLength = Math.Max(1, HiddenLayerSize);
_weightsInputHidden = new decimal[InputSize * hiddenLength];
_biasHidden = new decimal[hiddenLength];
_weightsHiddenOutput = new decimal[hiddenLength];
_hiddenOutputs = new decimal[hiddenLength];
var random = new Random(42);
for (var i = 0; i < _weightsInputHidden.Length; i++)
{
_weightsInputHidden[i] = (decimal)(random.NextDouble() * 0.1 - 0.05);
}
for (var j = 0; j < hiddenLength; j++)
{
_biasHidden[j] = (decimal)(random.NextDouble() * 0.1 - 0.05);
_weightsHiddenOutput[j] = (decimal)(random.NextDouble() * 0.1 - 0.05);
}
_biasOutput = (decimal)(random.NextDouble() * 0.1 - 0.05);
}
}
import clr
import math
import random
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class neural_network_atr_strategy(Strategy):
def __init__(self):
super(neural_network_atr_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._atr_period = self.Param("AtrPeriod", 14)
self._buy_threshold = self.Param("BuyThreshold", 0.502)
self._sell_threshold = self.Param("SellThreshold", 0.498)
self._hidden_layer_size = self.Param("HiddenLayerSize", 5)
self._input_size = self.Param("InputSize", 5)
self._feature_clamp = self.Param("FeatureClamp", 1.0)
self._initial_learning_rate = self.Param("InitialLearningRate", 0.01)
self._prev_close = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_open = 0.0
self._prev_volume = 0.0
self._has_prev = False
self._weights_ih = []
self._bias_h = []
self._weights_ho = []
self._bias_o = 0.0
self._learning_rate = 0.01
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def BuyThreshold(self):
return self._buy_threshold.Value
@BuyThreshold.setter
def BuyThreshold(self, value):
self._buy_threshold.Value = value
@property
def SellThreshold(self):
return self._sell_threshold.Value
@SellThreshold.setter
def SellThreshold(self, value):
self._sell_threshold.Value = value
@property
def HiddenLayerSize(self):
return self._hidden_layer_size.Value
@HiddenLayerSize.setter
def HiddenLayerSize(self, value):
self._hidden_layer_size.Value = value
@property
def InputSize(self):
return self._input_size.Value
@InputSize.setter
def InputSize(self, value):
self._input_size.Value = value
@property
def FeatureClamp(self):
return self._feature_clamp.Value
@FeatureClamp.setter
def FeatureClamp(self, value):
self._feature_clamp.Value = value
@property
def InitialLearningRate(self):
return self._initial_learning_rate.Value
@InitialLearningRate.setter
def InitialLearningRate(self, value):
self._initial_learning_rate.Value = value
def _init_network(self):
rng = random.Random(42)
inp = self.InputSize
hid = max(1, self.HiddenLayerSize)
self._weights_ih = [rng.uniform(-0.05, 0.05) for _ in range(inp * hid)]
self._bias_h = [rng.uniform(-0.05, 0.05) for _ in range(hid)]
self._weights_ho = [rng.uniform(-0.05, 0.05) for _ in range(hid)]
self._bias_o = rng.uniform(-0.05, 0.05)
self._learning_rate = float(self.InitialLearningRate)
def _normalize(self, value):
clamp = float(self.FeatureClamp)
if value > clamp:
value = clamp
elif value < -clamp:
value = -clamp
return (value + clamp) / (2.0 * clamp)
def _sigmoid(self, x):
return 1.0 / (1.0 + math.exp(-x))
def _predict(self, inputs):
hid = max(1, self.HiddenLayerSize)
inp_size = self.InputSize
hidden = [0.0] * hid
for j in range(hid):
act = self._bias_h[j]
for i in range(inp_size):
act += inputs[i] * self._weights_ih[i * hid + j]
hidden[j] = max(0.0, act)
output = self._bias_o
for j in range(hid):
output += hidden[j] * self._weights_ho[j]
pred = self._sigmoid(output)
adjusted = pred * (1.0 + self._learning_rate)
return max(0.0, min(1.0, adjusted))
def OnReseted(self):
super(neural_network_atr_strategy, self).OnReseted()
self._has_prev = False
self._init_network()
def OnStarted2(self, time):
super(neural_network_atr_strategy, self).OnStarted2(time)
self._has_prev = False
self._init_network()
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
vol = float(candle.TotalVolume)
atr_val = float(atr_value)
if not self._has_prev:
self._prev_close = close
self._prev_open = open_p
self._prev_high = high
self._prev_low = low
self._prev_volume = vol
self._has_prev = True
return
# Build inputs
price_change = (close - self._prev_close) / self._prev_close if self._prev_close != 0 else 0.0
candle_range = (high - low) / close if close != 0 else 0.0
body = (close - open_p) / (high - low) if high != low else 0.0
vol_change = (vol - self._prev_volume) / self._prev_volume if self._prev_volume > 0 else 0.0
atr_norm = atr_val / close if close != 0 else 0.0
inputs = [
self._normalize(price_change),
self._normalize(candle_range),
self._normalize(body),
self._normalize(vol_change),
self._normalize(atr_norm)
]
prediction = self._predict(inputs)
buy_thresh = float(self.BuyThreshold)
sell_thresh = float(self.SellThreshold)
if prediction >= buy_thresh and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prediction <= sell_thresh and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_open = open_p
self._prev_high = high
self._prev_low = low
self._prev_volume = vol
def CreateClone(self):
return neural_network_atr_strategy()