Ver no GitHub

Estratégia Crypto Analysis

Visão geral

Esta estratégia é um port para StockSharp do expert advisor "Crypto Analysis" do MetaTrader 4. Ela procura rompimentos que ocorrem depois que o preço toca a banda externa de Bollinger no timeframe principal de negociação, enquanto a estrutura de mercado permanece baixista (LWMA rápida abaixo da LWMA lenta). O sistema só permite operações quando um surto de momentum em timeframe superior e um filtro MACD em um timeframe ainda maior concordam com a direção desejada. Depois de entrar no mercado, a posição é gerenciada por um bloco de proteção em camadas que espelha o EA original: stops baseados em pips, trailing baseado em dinheiro, realocação para break-even e controles de drawdown da carteira.

Lógica de negociação

  • Timeframe de sinal: configurável (M15 por padrão). Todas as regras de entrada/saída são avaliadas nesses candles.
  • Gatilho de volatilidade: a mínima do candle anterior deve tocar ou perfurar a banda inferior de Bollinger (20, 2) para preparar uma configuração comprada; um toque na banda superior prepara uma configuração vendida.
  • Filtro de tendência: ambos os cenários exigem que a média móvel linearmente ponderada rápida (LWMA, padrão 6) permaneça abaixo da LWMA lenta (padrão 85), replicando a checagem de viés baixista do EA.
  • Confirmação RSI: RSI(14) precisa estar acima de 50 para compras e abaixo de 50 para vendas.
  • Surto de momentum: o desvio absoluto máximo dos três últimos valores Momentum(14) do timeframe superior em relação à linha-base 100 deve exceder os limiares de compra/venda. Isso captura os picos de momentum usados pelo código MQL.
  • Filtro MACD de timeframe superior: um MACD (12, 26, 9) separado sobre candles de um timeframe superior (H4 por padrão) confirma a direção; compras exigem MACD principal acima do sinal, vendas exigem o oposto.
  • Execução de entrada: quando todos os filtros se alinham, a estratégia abre uma ordem a mercado. Posições opostas são zeradas antes da reversão para manter uma única posição líquida, espelhando o comportamento do EA de fechar operações contrárias.

Gestão de posição

  • Stop e alvo iniciais: distâncias configuráveis em pips são convertidas a partir do tamanho de tick do instrumento usando o mesmo tratamento de 5 dígitos/3 dígitos do EA (passos 0.00001 e 0.001 são multiplicados por 10).
  • Trailing stop: após a formação de uma nova máxima (compra) ou mínima (venda), o stop é puxado atrás do preço por TrailingStopPips, sempre respeitando o melhor nível alcançado.
  • Break-even: quando o lucro atinge BreakEvenTriggerPips, o stop é movido para o preço de entrada mais BreakEvenOffsetPips (compra) ou menos o offset (venda).
  • Metas monetárias: limites opcionais de lucro absoluto ou percentual fecham a posição assim que o PnL flutuante atinge o nível solicitado.
  • Trailing monetário: quando o lucro não realizado excede MoneyTrailTarget, a estratégia acompanha o pico e fecha a posição se a devolução for igual ou superior a MoneyTrailStop.
  • Stop de patrimônio: o patrimônio flutuante (valor atual da carteira mais PnL não realizado) é monitorado; se o drawdown a partir do pico ultrapassar EquityRiskPercent, a posição é zerada.

Dados multi-timeframe

Três assinaturas são registradas automaticamente:

  1. Série principal de candles para as regras de Bollinger/LWMA/RSI.
  2. Candles de timeframe superior para o filtro de momentum (H1 por padrão).
  3. Candles de um timeframe superior para a confirmação MACD (H4 por padrão).

Parâmetros

Parâmetro Descrição
OrderVolume Tamanho base da ordem. Posições opostas são fechadas antes de abrir uma nova.
UseMoneyTakeProfit Habilita a meta absoluta de take-profit monetário.
MoneyTakeProfit Lucro na moeda da carteira que dispara uma saída quando UseMoneyTakeProfit é verdadeiro.
UsePercentTakeProfit Habilita a meta de take-profit percentual calculada a partir do patrimônio inicial.
PercentTakeProfit Percentual de lucro necessário para fechar a posição quando a meta percentual está ativa.
EnableMoneyTrailing Ativa o bloco de trailing baseado em dinheiro.
MoneyTrailTarget Nível de lucro que inicia o trailing monetário.
MoneyTrailStop Devolução máxima permitida do lucro depois que MoneyTrailTarget foi alcançado.
StopLossPips Distância inicial do stop-loss em pips.
TakeProfitPips Distância inicial do take-profit em pips.
TrailingStopPips Distância do trailing stop em pips.
UseBreakEven Habilita a realocação do stop para break-even.
BreakEvenTriggerPips Lucro em pips necessário antes de ativar a proteção break-even.
BreakEvenOffsetPips Pips adicionais somados ao preço de entrada ao posicionar o stop de break-even.
FastMaPeriod Comprimento da LWMA rápida calculada sobre preço típico.
SlowMaPeriod Comprimento da LWMA lenta calculada sobre preço típico.
MomentumPeriod Período do indicador Momentum no timeframe superior.
MomentumBuyThreshold Desvio mínimo de momentum para entradas compradas.
MomentumSellThreshold Desvio mínimo de momentum para entradas vendidas.
MacdFastLength Comprimento da EMA rápida para o filtro MACD de timeframe superior.
MacdSlowLength Comprimento da EMA lenta para o filtro MACD de timeframe superior.
MacdSignalLength Comprimento do sinal para o filtro MACD de timeframe superior.
UseEquityStop Habilita a proteção contra drawdown da carteira.
EquityRiskPercent Percentual permitido de drawdown do patrimônio antes de fechar a posição forçadamente.
CandleType Timeframe primário usado para entradas.
MomentumCandleType Timeframe superior usado para confirmação de momentum.
MacdCandleType Timeframe superior usado para confirmação MACD.

Notas

  • O port StockSharp mantém uma única posição líquida, correspondendo ao EA que fecha ordens opostas antes de abrir uma nova operação.
  • Todas as regras de proteção operam em candles fechados para replicar o processamento de "nova barra" do script original.
  • Ao usar símbolos sintéticos ou instrumentos sem tamanho de pip padrão, ajuste StopLossPips e parâmetros relacionados ao valor de tick da bolsa.
using System;
using System.Collections.Generic;
using System.Linq;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Crypto Analysis: Bollinger/LWMA/RSI entry with higher-timeframe Momentum and MACD filters.
/// </summary>
public class CryptoAnalysisStrategy : Strategy
{
	private const int _rsiLength = 14;
	private const int _bandLength = 20;
	private const decimal _bandWidth = 2m;

	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _useMoneyTakeProfit;
	private readonly StrategyParam<decimal> _moneyTakeProfit;
	private readonly StrategyParam<bool> _usePercentTakeProfit;
	private readonly StrategyParam<decimal> _percentTakeProfit;
	private readonly StrategyParam<bool> _enableMoneyTrailing;
	private readonly StrategyParam<decimal> _moneyTrailTarget;
	private readonly StrategyParam<decimal> _moneyTrailStop;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<bool> _useBreakEven;
	private readonly StrategyParam<int> _breakEvenTriggerPips;
	private readonly StrategyParam<int> _breakEvenOffsetPips;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _momentumBuyThreshold;
	private readonly StrategyParam<decimal> _momentumSellThreshold;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<bool> _useEquityStop;
	private readonly StrategyParam<decimal> _equityRiskPercent;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _momentumCandleType;
	private readonly StrategyParam<DataType> _macdCandleType;

	private readonly RelativeStrengthIndex _rsi = new() { Length = _rsiLength };
	private readonly List<Bar> _primary = [];
	private readonly List<decimal> _momentumCloses = [];
	private readonly Queue<decimal> _momentumDeviations = new();

	private decimal? _macdFast;
	private decimal? _macdSlow;
	private decimal? _macdSignal;
	private bool _macdReady;
	private int _macdCount;

	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _bestPrice;
	private decimal _initialEquity;
	private decimal _peakEquity;
	private decimal? _moneyTrailPeak;

	public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
	public bool UseMoneyTakeProfit { get => _useMoneyTakeProfit.Value; set => _useMoneyTakeProfit.Value = value; }
	public decimal MoneyTakeProfit { get => _moneyTakeProfit.Value; set => _moneyTakeProfit.Value = value; }
	public bool UsePercentTakeProfit { get => _usePercentTakeProfit.Value; set => _usePercentTakeProfit.Value = value; }
	public decimal PercentTakeProfit { get => _percentTakeProfit.Value; set => _percentTakeProfit.Value = value; }
	public bool EnableMoneyTrailing { get => _enableMoneyTrailing.Value; set => _enableMoneyTrailing.Value = value; }
	public decimal MoneyTrailTarget { get => _moneyTrailTarget.Value; set => _moneyTrailTarget.Value = value; }
	public decimal MoneyTrailStop { get => _moneyTrailStop.Value; set => _moneyTrailStop.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public int TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
	public bool UseBreakEven { get => _useBreakEven.Value; set => _useBreakEven.Value = value; }
	public int BreakEvenTriggerPips { get => _breakEvenTriggerPips.Value; set => _breakEvenTriggerPips.Value = value; }
	public int BreakEvenOffsetPips { get => _breakEvenOffsetPips.Value; set => _breakEvenOffsetPips.Value = value; }
	public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
	public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public decimal MomentumBuyThreshold { get => _momentumBuyThreshold.Value; set => _momentumBuyThreshold.Value = value; }
	public decimal MomentumSellThreshold { get => _momentumSellThreshold.Value; set => _momentumSellThreshold.Value = value; }
	public int MacdFastLength { get => _macdFastLength.Value; set => _macdFastLength.Value = value; }
	public int MacdSlowLength { get => _macdSlowLength.Value; set => _macdSlowLength.Value = value; }
	public int MacdSignalLength { get => _macdSignalLength.Value; set => _macdSignalLength.Value = value; }
	public bool UseEquityStop { get => _useEquityStop.Value; set => _useEquityStop.Value = value; }
	public decimal EquityRiskPercent { get => _equityRiskPercent.Value; set => _equityRiskPercent.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public DataType MomentumCandleType { get => _momentumCandleType.Value; set => _momentumCandleType.Value = value; }
	public DataType MacdCandleType { get => _macdCandleType.Value; set => _macdCandleType.Value = value; }

	public CryptoAnalysisStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero();
		_useMoneyTakeProfit = Param(nameof(UseMoneyTakeProfit), false);
		_moneyTakeProfit = Param(nameof(MoneyTakeProfit), 100m).SetNotNegative();
		_usePercentTakeProfit = Param(nameof(UsePercentTakeProfit), false);
		_percentTakeProfit = Param(nameof(PercentTakeProfit), 1m).SetNotNegative();
		_enableMoneyTrailing = Param(nameof(EnableMoneyTrailing), false);
		_moneyTrailTarget = Param(nameof(MoneyTrailTarget), 50m).SetNotNegative();
		_moneyTrailStop = Param(nameof(MoneyTrailStop), 20m).SetNotNegative();
		_stopLossPips = Param(nameof(StopLossPips), 50).SetNotNegative();
		_takeProfitPips = Param(nameof(TakeProfitPips), 100).SetNotNegative();
		_trailingStopPips = Param(nameof(TrailingStopPips), 30).SetNotNegative();
		_useBreakEven = Param(nameof(UseBreakEven), true);
		_breakEvenTriggerPips = Param(nameof(BreakEvenTriggerPips), 30).SetNotNegative();
		_breakEvenOffsetPips = Param(nameof(BreakEvenOffsetPips), 2).SetNotNegative();
		_fastMaPeriod = Param(nameof(FastMaPeriod), 6).SetGreaterThanZero();
		_slowMaPeriod = Param(nameof(SlowMaPeriod), 85).SetGreaterThanZero();
		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetGreaterThanZero();
		_momentumBuyThreshold = Param(nameof(MomentumBuyThreshold), 0.3m).SetNotNegative();
		_momentumSellThreshold = Param(nameof(MomentumSellThreshold), 0.3m).SetNotNegative();
		_macdFastLength = Param(nameof(MacdFastLength), 12).SetGreaterThanZero();
		_macdSlowLength = Param(nameof(MacdSlowLength), 26).SetGreaterThanZero();
		_macdSignalLength = Param(nameof(MacdSignalLength), 9).SetGreaterThanZero();
		_useEquityStop = Param(nameof(UseEquityStop), false);
		_equityRiskPercent = Param(nameof(EquityRiskPercent), 10m).SetNotNegative();
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
		_momentumCandleType = Param(nameof(MomentumCandleType), TimeSpan.FromHours(1).TimeFrame());
		_macdCandleType = Param(nameof(MacdCandleType), TimeSpan.FromHours(4).TimeFrame());
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType), (Security, MomentumCandleType), (Security, MacdCandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_rsi.Reset();
		_primary.Clear();
		_momentumCloses.Clear();
		_momentumDeviations.Clear();
		_macdFast = null;
		_macdSlow = null;
		_macdSignal = null;
		_macdReady = false;
		_macdCount = 0;
		ResetTradeState();
		_initialEquity = 0m;
		_peakEquity = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_initialEquity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		_peakEquity = _initialEquity;

		SubscribeCandles(MomentumCandleType).Bind(ProcessMomentum).Start();
		SubscribeCandles(MacdCandleType).Bind(ProcessMacd).Start();
		SubscribeCandles(CandleType).Bind(ProcessPrimary).Start();
	}

	private void ProcessMomentum(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_momentumCloses.Add(candle.ClosePrice);
		var keep = MomentumPeriod + 5;
		if (_momentumCloses.Count > keep)
			_momentumCloses.RemoveRange(0, _momentumCloses.Count - keep);

		if (_momentumCloses.Count <= MomentumPeriod)
			return;

		var previous = _momentumCloses[^(MomentumPeriod + 1)];
		if (previous == 0m)
			return;

		var momentum = candle.ClosePrice / previous * 100m;
		_momentumDeviations.Enqueue(Math.Abs(momentum - 100m));
		while (_momentumDeviations.Count > 3)
			_momentumDeviations.Dequeue();
	}

	private void ProcessMacd(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_macdCount++;
		_macdFast = Ema(_macdFast, candle.ClosePrice, MacdFastLength);
		_macdSlow = Ema(_macdSlow, candle.ClosePrice, MacdSlowLength);
		var main = _macdFast.Value - _macdSlow.Value;
		_macdSignal = Ema(_macdSignal, main, MacdSignalLength);
		_macdReady = _macdCount >= MacdSlowLength + MacdSignalLength;
	}

	private void ProcessPrimary(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var required = Math.Max(Math.Max(FastMaPeriod, SlowMaPeriod), _bandLength + 1);

		_primary.Add(new(candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice));
		if (_primary.Count > required + 5)
			_primary.RemoveRange(0, _primary.Count - required - 5);

		var rsiValue = _rsi.Process(candle.ClosePrice, candle.ServerTime, true);

		if (Position != 0 && ApplyRisk(candle))
			return;

		if (_primary.Count < required ||
			!_rsi.IsFormed ||
			_momentumDeviations.Count < 3 ||
			!_macdReady ||
			_macdSignal is null ||
			_macdFast is null ||
			_macdSlow is null)
			return;

		var previous = _primary[^2];
		var (upper, lower) = BollingerAtPrevious();
		var fast = Lwma(FastMaPeriod);
		var slow = Lwma(SlowMaPeriod);
		var rsi = rsiValue.ToDecimal();
		var momentum = _momentumDeviations.Max();
		var macdMain = _macdFast.Value - _macdSlow.Value;
		var macdSignal = _macdSignal.Value;

		var longSignal =
			previous.Low <= lower &&
			fast < slow &&
			rsi > 50m &&
			momentum > MomentumBuyThreshold &&
			macdMain > macdSignal;

		var shortSignal =
			previous.High >= upper &&
			fast < slow &&
			rsi < 50m &&
			momentum > MomentumSellThreshold &&
			macdMain < macdSignal;

		if (longSignal && Position <= 0m)
			Enter(Sides.Buy, candle.ClosePrice);
		else if (shortSignal && Position >= 0m)
			Enter(Sides.Sell, candle.ClosePrice);
	}

	private void Enter(Sides side, decimal price)
	{
		var volume = OrderVolume + Math.Abs(Position);
		if (side == Sides.Buy)
			BuyMarket(volume);
		else
			SellMarket(volume);

		_entryPrice = price;
		var pip = GetPipSize();
		_stopPrice = StopLossPips > 0
			? side == Sides.Buy ? price - StopLossPips * pip : price + StopLossPips * pip
			: null;
		_takePrice = TakeProfitPips > 0
			? side == Sides.Buy ? price + TakeProfitPips * pip : price - TakeProfitPips * pip
			: null;
		_bestPrice = price;
		_moneyTrailPeak = null;
	}

	private bool ApplyRisk(ICandleMessage candle)
	{
		var floating = FloatingPnL(candle.ClosePrice);
		var equity = (Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? _initialEquity) + floating;
		_peakEquity = Math.Max(_peakEquity, equity);

		// The levels are checked as they stood before this candle; moving them afterwards keeps
		// a move made from this candle's high or low from being hit by the same candle.
		if (IsLevelHit(candle))
			return Flatten();

		if (UseMoneyTakeProfit && floating >= MoneyTakeProfit)
			return Flatten();

		if (UsePercentTakeProfit && _initialEquity > 0m && floating >= _initialEquity * PercentTakeProfit / 100m)
			return Flatten();

		if (EnableMoneyTrailing)
		{
			// Once armed, the peak is tracked and the giveback checked even below the target.
			if (_moneyTrailPeak is decimal peak)
				_moneyTrailPeak = Math.Max(peak, floating);
			else if (floating > MoneyTrailTarget)
				_moneyTrailPeak = floating;

			if (_moneyTrailPeak is decimal armedPeak && armedPeak - floating >= MoneyTrailStop)
				return Flatten();
		}

		if (UseEquityStop && _peakEquity > 0m && (_peakEquity - equity) / _peakEquity * 100m > EquityRiskPercent)
			return Flatten();

		MoveLevels(candle);
		return false;
	}

	private bool IsLevelHit(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			return (_stopPrice is decimal longStop && candle.LowPrice <= longStop) ||
				(_takePrice is decimal longTake && candle.HighPrice >= longTake);
		}

		return (_stopPrice is decimal shortStop && candle.HighPrice >= shortStop) ||
			(_takePrice is decimal shortTake && candle.LowPrice <= shortTake);
	}

	private void MoveLevels(ICandleMessage candle)
	{
		var pip = GetPipSize();

		if (Position > 0m)
		{
			if (UseBreakEven && candle.HighPrice - _entryPrice >= BreakEvenTriggerPips * pip)
				RaiseStop(_entryPrice + BreakEvenOffsetPips * pip);

			// The stop trails only a new high beyond the best price since the entry.
			if (candle.HighPrice > _bestPrice)
			{
				_bestPrice = candle.HighPrice;

				if (TrailingStopPips > 0)
					RaiseStop(_bestPrice - TrailingStopPips * pip);
			}
		}
		else
		{
			if (UseBreakEven && _entryPrice - candle.LowPrice >= BreakEvenTriggerPips * pip)
				LowerStop(_entryPrice - BreakEvenOffsetPips * pip);

			if (candle.LowPrice < _bestPrice)
			{
				_bestPrice = candle.LowPrice;

				if (TrailingStopPips > 0)
					LowerStop(_bestPrice + TrailingStopPips * pip);
			}
		}
	}

	private void RaiseStop(decimal level)
	{
		if (_stopPrice is not decimal stop || level > stop)
			_stopPrice = level;
	}

	private void LowerStop(decimal level)
	{
		if (_stopPrice is not decimal stop || level < stop)
			_stopPrice = level;
	}

	private bool Flatten()
	{
		if (Position > 0m)
			SellMarket(Math.Abs(Position));
		else if (Position < 0m)
			BuyMarket(Math.Abs(Position));
		else
			return false;

		ResetTradeState();
		return true;
	}

	private decimal FloatingPnL(decimal price)
	{
		if (Position == 0m || _entryPrice == 0m)
			return 0m;

		var direction = Position > 0m ? 1m : -1m;
		return (price - _entryPrice) * direction * Math.Abs(Position) * (Security?.Multiplier ?? 1m);
	}

	private (decimal Upper, decimal Lower) BollingerAtPrevious()
	{
		var end = _primary.Count - 1;
		var total = 0m;
		for (var i = end - _bandLength; i < end; i++)
			total += _primary[i].Close;

		var mean = total / _bandLength;
		var squares = 0m;
		for (var i = end - _bandLength; i < end; i++)
		{
			var deviation = _primary[i].Close - mean;
			squares += deviation * deviation;
		}

		var std = (decimal)Math.Sqrt((double)(squares / _bandLength));
		return (mean + _bandWidth * std, mean - _bandWidth * std);
	}

	private decimal Lwma(int period)
	{
		var start = _primary.Count - period;
		var total = 0m;
		var weights = 0m;
		for (var i = 0; i < period; i++)
		{
			var weight = i + 1m;
			total += _primary[start + i].Typical * weight;
			weights += weight;
		}
		return total / weights;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m) return 0.0001m;
		return step is 0.00001m or 0.001m ? step * 10m : step;
	}

	private static decimal Ema(decimal? previous, decimal value, int period)
	{
		if (previous is null) return value;
		var alpha = 2m / (period + 1m);
		return previous.Value + alpha * (value - previous.Value);
	}

	private void ResetTradeState()
	{
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_bestPrice = 0m;
		_moneyTrailPeak = null;
	}

	private readonly record struct Bar(decimal Open, decimal High, decimal Low, decimal Close)
	{
		public decimal Typical => (High + Low + Close) / 3m;
	}
}