Ver no GitHub

Estratégia do arco-íris do seguidor de tendências

Visão geral

Trend Follower Rainbow Strategy é uma versão C# do consultor especialista MetaTrader 4 "TrendFollowerRainbowMethodkyast773". A estratégia combina várias camadas de confirmação para negociar na direção de tendências fortes, ao mesmo tempo que filtra os períodos limitados. Ele se baseia no alinhamento de um arco-íris de médias móveis exponenciais, impulso MACD, limites do oscilador Laguerre, leituras do índice de fluxo de dinheiro e um cruzamento rápido/EMA lenta para acionar posições.

Lógica de negociação

  1. Janela de negociação – Os sinais são avaliados apenas quando o tempo de fechamento da vela atual está estritamente entre os horários de início e término configuráveis. Isso imita o filtro de tempo original do EA que evitou o primeiro e o último horário de negociação da sessão.
  2. EMA Gatilho de cruzamento – Uma configuração longa requer que o EMA rápido (comprimento padrão 4) cruze acima do EMA lento (comprimento padrão 8). Uma configuração curta requer o cruzamento oposto.
  3. MACD Confirmação – A linha MACD e a linha de sinal (padrão 35/05/5) devem estar acima de zero para negociações longas ou abaixo de zero para negociações curtas para confirmar o alinhamento do impulso.
  4. Filtro Laguerre – O valor do filtro Laguerre deve ultrapassar 0,15 para negociações longas ou abaixo de 0,75 para negociações curtas, reproduzindo as verificações de limite originais realizadas no indicador personalizado.
  5. Alinhamento do arco-íris – Cinco pacotes de médias móveis exponenciais (quatro EMAs por pacote) devem ser classificados monotonicamente para confirmar a estrutura do arco-íris. Os pacotes são avaliados para ordem não crescente em cenários de alta e ordem não decrescente em cenários de baixa.
  6. Filtro do Índice de Fluxo de Dinheiro – O Índice de Fluxo de Dinheiro (período padrão 14) deve estar abaixo de 40 para entradas longas e acima de 60 para entradas curtas para evitar negociação contra o fluxo orientado por volume.
  7. Gerenciamento de posição – São utilizadas ordens de mercado. Quando aparece um sinal oposto, a exposição existente é fechada e uma nova posição é aberta na direção oposta.

Gestão de risco

A estratégia oferece suporte a proteções integradas por meio do auxiliar StartProtection de StockSharp:

  • As distâncias de Take Profit e Stop Loss são expressas em etapas de preço para espelhar a configuração baseada em pontos do EA.
  • A distância Trailing Stop também usa etapas de preço e é ativada assim que o bloqueio de proteção é iniciado.

Parâmetros

Parâmetro Descrição Padrão
OrderVolume Volume base de ordens de mercado. 1
TakeProfitPoints Calcule a distância do lucro nas etapas de preço. 17
StopLossPoints Pare a distância de perda nas etapas de preço. 30
TrailingStopPoints Distância do trailing stop em etapas de preço. 45
TradingStartHour Primeira hora (inclusive) que é ignorada antes da avaliação dos sinais. 1
TradingEndHour Última hora (inclusive) que é ignorada após a avaliação dos sinais. 23
FastEmaLength Comprimento do EMA rápido usado no gatilho de cruzamento. 4
SlowEmaLength Comprimento do EMA lento usado no gatilho de cruzamento. 8
MacdFastLength MACD comprimento EMA rápido. 5
MacdSlowLength MACD comprimento EMA lento. 35
MacdSignalLength MACD comprimento do sinal EMA. 5
LaguerreGamma Fator de suavização do filtro Laguerre. 0,7
LaguerreBuyThreshold O limite de Laguerre ultrapassou para cima em negociações longas. 0,15
LaguerreSellThreshold O limite de Laguerre ultrapassou para baixo para negociações curtas. 0,75
MfiPeriod Período de cálculo do Índice de Fluxo de Dinheiro. 14
MfiBuyLevel Nível máximo de IMF que ainda permite entradas longas. 40
MfiSellLevel Nível mínimo de IMF que ainda permite entradas curtas. 60
RainbowGroup{1..5}Base Comprimento base de EMA para cada pacote arco-íris. Quatro EMAs consecutivos são criados a partir de cada valor base adicionando compensações (0, 2, 4, 6). 5/13/21/34/55
CandleType Série de velas primárias usadas pela estratégia. O padrão é velas de 5 minutos. Período de 5 minutos

Gráficos

A estratégia desenha automaticamente:

  • Velas de preço para a série assinada.
  • EMAs rápidos e lentos para confirmação visual de cruzamentos.
  • Valores do filtro Laguerre para observar cruzamentos de limites.
  • Negociações próprias plotadas na área do gráfico.

Notas

  • A lógica do arco-íris se aproxima dos indicadores personalizados originais do RainbowMMA construindo pacotes EMA configuráveis. Ajuste os comprimentos da base para corresponder a um modelo de arco-íris específico, se necessário.
  • Todos os comentários de código, logs e documentação são fornecidos em inglês, conforme necessário.
  • A estratégia concentra-se exclusivamente na implementação do C#. Nenhuma porta Python é gerada nesta tarefa.
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>
/// Trend following strategy that combines EMA crossover, MACD confirmation,
/// Laguerre filter thresholds, rainbow moving average structure and MFI filter.
/// </summary>
public class TrendFollowerRainbowStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<int> _tradingStartHour;
	private readonly StrategyParam<int> _tradingEndHour;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<decimal> _laguerreGamma;
	private readonly StrategyParam<decimal> _laguerreBuyThreshold;
	private readonly StrategyParam<decimal> _laguerreSellThreshold;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiBuyLevel;
	private readonly StrategyParam<decimal> _mfiSellLevel;
	private readonly StrategyParam<int> _rainbowGroup1Base;
	private readonly StrategyParam<int> _rainbowGroup2Base;
	private readonly StrategyParam<int> _rainbowGroup3Base;
	private readonly StrategyParam<int> _rainbowGroup4Base;
	private readonly StrategyParam<int> _rainbowGroup5Base;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _emaFast = null!;
	private ExponentialMovingAverage _emaSlow = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private AdaptiveLaguerreFilter _laguerre = null!;
	private MoneyFlowIndex _mfi = null!;
	private ExponentialMovingAverage[][] _rainbowGroups = [];

	private decimal? _previousFastEma;
	private decimal? _previousSlowEma;
	private decimal? _previousLaguerre;
	private decimal _pointValue;

	/// <summary>
	/// Initializes a new instance of the <see cref="TrendFollowerRainbowStrategy"/> class.
	/// </summary>
	public TrendFollowerRainbowStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetDisplay("Order Volume", "Base order volume", "Trading")
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 17m)
		.SetDisplay("Take Profit (pts)", "Distance in price steps for take profit", "Risk Management")
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
		.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop loss", "Risk Management")
		;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 45m)
		.SetDisplay("Trailing Stop (pts)", "Distance in price steps for trailing stop", "Risk Management")
		;

		_tradingStartHour = Param(nameof(TradingStartHour), 1)
		.SetDisplay("Start Hour", "Hour (0-23) when trading window opens", "Trading Schedule")
		;

		_tradingEndHour = Param(nameof(TradingEndHour), 23)
		.SetDisplay("End Hour", "Hour (0-23) when trading window closes", "Trading Schedule")
		;

		_fastEmaLength = Param(nameof(FastEmaLength), 4)
		.SetRange(2, 20)
		.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
		;

		_slowEmaLength = Param(nameof(SlowEmaLength), 8)
		.SetRange(3, 50)
		.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
		;

		_macdFastLength = Param(nameof(MacdFastLength), 5)
		.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
		;

		_macdSlowLength = Param(nameof(MacdSlowLength), 35)
		.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
		;

		_macdSignalLength = Param(nameof(MacdSignalLength), 5)
		.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
		;

		_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
		.SetRange(0.1m, 0.9m)
		.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre filter", "Indicators")
		;

		_laguerreBuyThreshold = Param(nameof(LaguerreBuyThreshold), 0.15m)
		.SetDisplay("Laguerre Buy", "Threshold crossed upward for long signals", "Indicators")
		;

		_laguerreSellThreshold = Param(nameof(LaguerreSellThreshold), 0.75m)
		.SetDisplay("Laguerre Sell", "Threshold crossed downward for short signals", "Indicators")
		;

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetDisplay("MFI Period", "Money Flow Index calculation period", "Indicators")
		;

		_mfiBuyLevel = Param(nameof(MfiBuyLevel), 40m)
		.SetDisplay("MFI Buy", "Upper bound for oversold check", "Indicators")
		;

		_mfiSellLevel = Param(nameof(MfiSellLevel), 60m)
		.SetDisplay("MFI Sell", "Lower bound for overbought check", "Indicators")
		;

		_rainbowGroup1Base = Param(nameof(RainbowGroup1Base), 5)
		.SetDisplay("Rainbow Group 1", "Base length for the fastest rainbow bundle", "Rainbow")
		;

		_rainbowGroup2Base = Param(nameof(RainbowGroup2Base), 13)
		.SetDisplay("Rainbow Group 2", "Base length for the second rainbow bundle", "Rainbow")
		;

		_rainbowGroup3Base = Param(nameof(RainbowGroup3Base), 21)
		.SetDisplay("Rainbow Group 3", "Base length for the middle rainbow bundle", "Rainbow")
		;

		_rainbowGroup4Base = Param(nameof(RainbowGroup4Base), 34)
		.SetDisplay("Rainbow Group 4", "Base length for the fourth rainbow bundle", "Rainbow")
		;

		_rainbowGroup5Base = Param(nameof(RainbowGroup5Base), 55)
		.SetDisplay("Rainbow Group 5", "Base length for the slowest rainbow bundle", "Rainbow")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Primary candle series", "General");
	}

	/// <summary>
	/// Base order volume.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in price steps.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// First hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingStartHour
	{
		get => _tradingStartHour.Value;
		set => _tradingStartHour.Value = value;
	}

	/// <summary>
	/// Last hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingEndHour
	{
		get => _tradingEndHour.Value;
		set => _tradingEndHour.Value = value;
	}

	/// <summary>
	/// Fast EMA length used for the crossover signal.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length used for the crossover signal.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// MACD fast EMA length.
	/// </summary>
	public int MacdFastLength
	{
		get => _macdFastLength.Value;
		set => _macdFastLength.Value = value;
	}

	/// <summary>
	/// MACD slow EMA length.
	/// </summary>
	public int MacdSlowLength
	{
		get => _macdSlowLength.Value;
		set => _macdSlowLength.Value = value;
	}

	/// <summary>
	/// MACD signal EMA length.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Laguerre filter smoothing factor.
	/// </summary>
	public decimal LaguerreGamma
	{
		get => _laguerreGamma.Value;
		set => _laguerreGamma.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed upward to allow long signals.
	/// </summary>
	public decimal LaguerreBuyThreshold
	{
		get => _laguerreBuyThreshold.Value;
		set => _laguerreBuyThreshold.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed downward to allow short signals.
	/// </summary>
	public decimal LaguerreSellThreshold
	{
		get => _laguerreSellThreshold.Value;
		set => _laguerreSellThreshold.Value = value;
	}

	/// <summary>
	/// Money Flow Index period.
	/// </summary>
	public int MfiPeriod
	{
		get => _mfiPeriod.Value;
		set => _mfiPeriod.Value = value;
	}

	/// <summary>
	/// Maximum MFI level that still allows long entries.
	/// </summary>
	public decimal MfiBuyLevel
	{
		get => _mfiBuyLevel.Value;
		set => _mfiBuyLevel.Value = value;
	}

	/// <summary>
	/// Minimum MFI level that still allows short entries.
	/// </summary>
	public decimal MfiSellLevel
	{
		get => _mfiSellLevel.Value;
		set => _mfiSellLevel.Value = value;
	}

	/// <summary>
	/// Base period for the fastest rainbow bundle.
	/// </summary>
	public int RainbowGroup1Base
	{
		get => _rainbowGroup1Base.Value;
		set => _rainbowGroup1Base.Value = value;
	}

	/// <summary>
	/// Base period for the second rainbow bundle.
	/// </summary>
	public int RainbowGroup2Base
	{
		get => _rainbowGroup2Base.Value;
		set => _rainbowGroup2Base.Value = value;
	}

	/// <summary>
	/// Base period for the third rainbow bundle.
	/// </summary>
	public int RainbowGroup3Base
	{
		get => _rainbowGroup3Base.Value;
		set => _rainbowGroup3Base.Value = value;
	}

	/// <summary>
	/// Base period for the fourth rainbow bundle.
	/// </summary>
	public int RainbowGroup4Base
	{
		get => _rainbowGroup4Base.Value;
		set => _rainbowGroup4Base.Value = value;
	}

	/// <summary>
	/// Base period for the fifth rainbow bundle.
	/// </summary>
	public int RainbowGroup5Base
	{
		get => _rainbowGroup5Base.Value;
		set => _rainbowGroup5Base.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousFastEma = null;
		_previousSlowEma = null;
		_previousLaguerre = null;
		_pointValue = 0m;
		_rainbowGroups = [];
	}

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

		_pointValue = Security?.PriceStep ?? 0m;
		Volume = OrderVolume;

		var takeProfit = ToAbsoluteUnit(TakeProfitPoints);
		var stopLoss = ToAbsoluteUnit(StopLossPoints);

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(
			takeProfit: takeProfit,
			stopLoss: stopLoss,
			isStopTrailing: TrailingStopPoints > 0m,
			useMarketOrders: true);
		}

		_emaFast = new EMA { Length = FastEmaLength };
		_emaSlow = new EMA { Length = SlowEmaLength };
		_macd = new MovingAverageConvergenceDivergenceSignal();
		_macd.Macd.ShortMa.Length = MacdFastLength;
		_macd.Macd.LongMa.Length = MacdSlowLength;
		_macd.SignalMa.Length = MacdSignalLength;
		_laguerre = new AdaptiveLaguerreFilter { Gamma = LaguerreGamma };
		_mfi = new MoneyFlowIndex { Length = MfiPeriod };
		_rainbowGroups = BuildRainbowGroups();

		var indicators = new List<IIndicator>
		{
			_emaFast,
			_emaSlow,
			_macd,
			_laguerre,
			_mfi
		};

		foreach (var group in _rainbowGroups)
		{
			indicators.AddRange(group);
		}

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(indicators.ToArray(), ProcessCandle)
		.Start();

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

	private ExponentialMovingAverage[][] BuildRainbowGroups()
	{
		var offsets = new[] { 0, 2, 4, 6 };

		return new[]
		{
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup1Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup2Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup3Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup4Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup5Base + o) }).ToArray()
		};
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var hour = candle.CloseTime.Hour;
		if (hour <= TradingStartHour || hour >= TradingEndHour)
		{
			UpdatePreviousValues(values);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			UpdatePreviousValues(values);
			return;
		}

		var index = 0;

		var hasFast = TryGetDecimal(values[index++], out var fastEma);
		var hasSlow = TryGetDecimal(values[index++], out var slowEma);
		if (!hasFast || !hasSlow)
		{
			UpdatePreviousValues(values, hasFast ? fastEma : null, hasSlow ? slowEma : null);
			return;
		}

		var macdValue = values[index++];
		if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData ||
		macdData.Macd is not decimal macdMain || macdData.Signal is not decimal macdSignal)
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var laguerre))
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var mfi))
		{
			UpdatePreviousValues(values, fastEma, slowEma, laguerre);
			return;
		}

		var rainbowValues = new List<decimal[]>(_rainbowGroups.Length);
		for (var groupIndex = 0; groupIndex < _rainbowGroups.Length; groupIndex++)
		{
			var group = _rainbowGroups[groupIndex];
			var decimals = new decimal[group.Length];

			for (var i = 0; i < group.Length; i++)
			{
				if (!TryGetDecimal(values[index++], out var rainbow))
				{
					UpdatePreviousValues(values, fastEma, slowEma, laguerre);
					return;
				}

				decimals[i] = rainbow;
			}

			rainbowValues.Add(decimals);
		}

		var rainbowBullish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: true));
		var rainbowBearish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: false));

		var emaCrossUp = _previousFastEma is decimal prevFast && _previousSlowEma is decimal prevSlow &&
		prevFast < prevSlow && fastEma > slowEma;

		var emaCrossDown = _previousFastEma is decimal prevFastDown && _previousSlowEma is decimal prevSlowDown &&
		prevFastDown > prevSlowDown && fastEma < slowEma;

		var laguerreBullish = _previousLaguerre is decimal prevLagBull &&
		prevLagBull <= LaguerreBuyThreshold && laguerre > LaguerreBuyThreshold;

		var laguerreBearish = _previousLaguerre is decimal prevLagBear &&
		prevLagBear >= LaguerreSellThreshold && laguerre < LaguerreSellThreshold;

		var macdBullish = macdMain > 0m && macdSignal > 0m;
		var macdBearish = macdMain < 0m && macdSignal < 0m;

		var mfiBullish = mfi < MfiBuyLevel;
		var mfiBearish = mfi > MfiSellLevel;

		if (emaCrossUp && macdBullish && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
		}
		else if (emaCrossDown && macdBearish && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
		}

		_previousFastEma = fastEma;
		_previousSlowEma = slowEma;
		_previousLaguerre = laguerre;
	}

	private void UpdatePreviousValues(IIndicatorValue[] values, decimal? fastEma = null, decimal? slowEma = null, decimal? laguerre = null)
	{
		var index = 0;

		fastEma ??= TryGetDecimal(values[index++], out var fast) ? fast : null;
		slowEma ??= TryGetDecimal(values[index++], out var slow) ? slow : null;
		index++;
		laguerre ??= TryGetDecimal(values[index++], out var lag) ? lag : null;

		_previousFastEma = fastEma ?? _previousFastEma;
		_previousSlowEma = slowEma ?? _previousSlowEma;
		_previousLaguerre = laguerre ?? _previousLaguerre;
	}

	private bool IsMonotonic(decimal[] values, bool descending)
	{
		for (var i = 0; i < values.Length - 1; i++)
		{
			if (descending)
			{
				if (values[i] < values[i + 1])
				return false;
			}
			else
			{
				if (values[i] > values[i + 1])
				return false;
			}
		}

		return true;
	}

	private static bool TryGetDecimal(IIndicatorValue value, out decimal result)
	{
		if (!value.IsFinal)
		{
			result = default;
			return false;
		}

		result = value.ToDecimal();
		return true;
	}

	private Unit ToAbsoluteUnit(decimal points)
	{
		if (points <= 0m || _pointValue <= 0m)
		return null;

		return new Unit(points * _pointValue, UnitTypes.Absolute);
	}
}