在 GitHub 上查看

Kijun Sen Robot 策略

概述

Kijun Sen Robot 策略 是将 MetaTrader 5 专家顾问 "Kijun Sen Robot" 迁移到 StockSharp 高级策略 API 的版本。默认使用 30 分钟 K 线,通过观察价格突破 Ichimoku 基准线(Kijun-sen)并结合 20 周期线性加权均线(LWMA)确认趋势来执行交易。策略保留了原始 EA 仅在活跃交易时段操作、并使用动态止损、保本和移动止损保护仓位的理念。

指标与数据

  • Ichimoku:Tenkan/Kijun/Senkou Span B 默认周期为 6/12/24。
  • 线性加权移动平均线(LWMA):20 根 K 线,用于确认趋势方向及与 Kijun 的距离。
  • K 线数据:默认使用 30 分钟周期,可通过 CandleType 参数切换其他时间框架。

交易逻辑

多头入场

  1. 当根 K 线从下方穿越 Kijun 线(开盘在下方、收盘在上方或盘中触及),且上一根 K 线收盘也位于 Kijun 下方。
  2. 当前 Kijun 相比两根之前持平或抬升。
  3. LWMA 至少低于 Kijun MaFilterPips(按品种最小价位换算)的距离。
  4. LWMA 斜率为正,即当前值高于上一根的值。
  5. 当前时间位于 TradingStartHourTradingEndHour 之间(默认 07:00–19:00)。

满足条件并且当前净头寸 ≤ 0 时,策略会以市价买入(若存在空头会先回补)。入场价格取当前 K 线收盘价。

空头入场

  1. 当前 K 线自上向下穿越 Kijun(逻辑与多头相反)。
  2. Kijun 相比两根之前持平或下移。
  3. LWMA 至少高于 Kijun MaFilterPips 的距离。
  4. LWMA 斜率为负,即当前值低于上一根的值。
  5. 仅在允许时间窗口内触发。

符合条件且净头寸 ≥ 0 时,策略以市价卖出(若存在多头会先平仓)。

仓位管理与退出

  • 初始止损:在入场价下方(做多)或上方(做空)放置 StopLossPips 的距离,按照品种 PriceStep 换算为价格,复刻原 EA 的保护性止损。
  • 保本移动:当浮盈达到 BreakEvenPips 后,将止损上调到入场价上方一个点(做多)或入场价下方一个点(做空)。
  • 移动止损:浮盈达到 TrailingStopPips 时,止损随价格按照该距离移动,仅朝有利方向调整。
  • 固定止盈TakeProfitPips 指定的盈利目标,设为 0 可关闭。
  • Kijun 斜率退出:若 LWMA 在止损仍低于入场价前反向转折,则立即平仓,保留原 EA 的防御逻辑。
  • 时间过滤:允许时段外不再开新仓,但已有仓位仍会持续执行上述保护规则。
  • 订单类型:全部使用市价单;原策略根据报价选择挂单或市价的细节被简化,因为 StockSharp 使用 K 线数据而非逐笔行情。

若同一根 K 线同时触及止损与止盈,策略优先触发止损,以便在无盘中信息时采取保守处理。

参数

参数 默认值 说明
TenkanPeriod 6 Ichimoku Tenkan 周期。
KijunPeriod 12 Ichimoku Kijun 周期。
SenkouSpanBPeriod 24 Ichimoku Senkou Span B 周期。
LwmaPeriod 20 LWMA 趋势确认周期。
MaFilterPips 6 Kijun 与 LWMA 最小距离(单位:点)。
StopLossPips 50 初始保护性止损。
BreakEvenPips 9 触发保本的浮盈距离。
TrailingStopPips 10 移动止损距离。
TakeProfitPips 120 固定止盈距离(0 = 关闭)。
TradingStartHour 7 允许交易起始小时(包含)。
TradingEndHour 19 允许交易结束小时(不包含)。
CandleType 30 分钟 信号使用的 K 线类型。

所有以点数表示的参数均根据品种的 PriceStep 转换成价格单位。当品种最小报价精度为 3 或 5 位小数时,会自动乘以 10,以模拟 MT5 中 digits_adjust 的处理方式。

实现说明

  • _pendingLongLevel_pendingShortLevel 用于模拟原 EA 中的 longcross/shortcross 状态变量,保证每次开仓都必须等待新的 Kijun 穿越。
  • MT5 中基于最后买/卖价的判断改为使用 K 线的开、高、低、收,适合在 StockSharp 回测环境下的确定性逻辑。
  • 止损、保本和移动止损由策略内部跟踪并通过 ClosePosition() 执行,而不是修改服务器端挂单。
  • ConvertPips 使用 Security.PriceStepSecurity.MinPriceStep 并结合 3/5 位小数的 10 倍调整,复刻原策略的点值换算。
  • 通过 SubscribeCandles().BindEx(...) 绑定 Ichimoku 与 LWMA 指标,同时在图表区域绘制 K 线、指标与自有成交。

使用建议

  1. 选择支持 30 分钟 K 线的交易品种,可按需修改 CandleType
  2. 启动前设置策略的 Volume 属性为期望下单手数。
  3. 根据品种波动或已有优化结果调整相关点数参数。
  4. 在高阶回测或实时环境中运行,策略会自动执行时间过滤与仓位保护逻辑。
  5. 通过日志或图表观察保本/移动止损的触发情况。按照要求,代码中的注释全部使用英文。

本文件夹仅包含 C# 版本,未提供 Python 实现。

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>
/// Ichimoku Kijun-sen robot converted from MetaTrader.
/// The strategy looks for price crossing the Kijun line while a 20-period LWMA confirms the trend.
/// It manages risk with time filters, configurable stop-loss, break-even and trailing rules.
/// </summary>
public class KijunSenRobotStrategy : Strategy
{
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<int> _lwmaPeriod;
	private readonly StrategyParam<decimal> _maFilterPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _breakEvenPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<int> _tradingStartHour;
	private readonly StrategyParam<int> _tradingEndHour;
	private readonly StrategyParam<DataType> _candleType;

	private Ichimoku _ichimoku = null!;
	private WeightedMovingAverage _lwma = null!;

	private decimal? _previousClose;
	private decimal? _previousMa;
	private decimal? _previousPrevMa;
	private decimal? _previousKijun;
	private decimal? _previousPrevKijun;
	private decimal? _pendingLongLevel;
	private decimal? _pendingShortLevel;

	private bool? _isLongPosition;
	private decimal? _entryPrice;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal _stopLossDistance;
	private decimal _takeProfitDistance;
	private decimal _breakEvenDistance;
	private decimal _breakEvenStep;
	private decimal _trailingDistance;
	private bool _breakEvenApplied;

	/// <summary>
	/// Tenkan-sen calculation period.
	/// </summary>
	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}

	/// <summary>
	/// Kijun-sen calculation period.
	/// </summary>
	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}

	/// <summary>
	/// Senkou Span B calculation period.
	/// </summary>
	public int SenkouSpanBPeriod
	{
		get => _senkouSpanBPeriod.Value;
		set => _senkouSpanBPeriod.Value = value;
	}

	/// <summary>
	/// Weighted moving average period used for slope confirmation.
	/// </summary>
	public int LwmaPeriod
	{
		get => _lwmaPeriod.Value;
		set => _lwmaPeriod.Value = value;
	}

	/// <summary>
	/// Minimum distance in pips between price and Kijun required by the LWMA filter.
	/// </summary>
	public decimal MaFilterPips
	{
		get => _maFilterPips.Value;
		set => _maFilterPips.Value = value;
	}

	/// <summary>
	/// Initial stop-loss in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Profit distance in pips required to move the stop-loss to break-even.
	/// </summary>
	public decimal BreakEvenPips
	{
		get => _breakEvenPips.Value;
		set => _breakEvenPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// First trading hour (inclusive) in exchange time.
	/// </summary>
	public int TradingStartHour
	{
		get => _tradingStartHour.Value;
		set => _tradingStartHour.Value = value;
	}

	/// <summary>
	/// Last trading hour (exclusive) in exchange time.
	/// </summary>
	public int TradingEndHour
	{
		get => _tradingEndHour.Value;
		set => _tradingEndHour.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="KijunSenRobotStrategy"/>.
	/// </summary>
	public KijunSenRobotStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 6)
		.SetGreaterThanZero()
		.SetDisplay("Tenkan Period", "Period for Ichimoku Tenkan line", "Ichimoku")
		
		.SetOptimize(4, 12, 1);

		_kijunPeriod = Param(nameof(KijunPeriod), 12)
		.SetGreaterThanZero()
		.SetDisplay("Kijun Period", "Period for Ichimoku Kijun line", "Ichimoku")
		
		.SetOptimize(8, 20, 1);

		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 24)
		.SetGreaterThanZero()
		.SetDisplay("Senkou Span B Period", "Period for Ichimoku Senkou Span B", "Ichimoku")
		
		.SetOptimize(18, 30, 1);

		_lwmaPeriod = Param(nameof(LwmaPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("LWMA Period", "Length of the confirmation LWMA", "Trend Filter")
		
		.SetOptimize(10, 40, 2);

		_maFilterPips = Param(nameof(MaFilterPips), 20m)
		.SetNotNegative()
		.SetDisplay("LWMA Filter (pips)", "Minimum distance between price and Kijun required by the LWMA", "Trend Filter")
		
		.SetOptimize(0m, 20m, 1m);

		_stopLossPips = Param(nameof(StopLossPips), 50m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Initial protective stop distance", "Risk Management")
		
		.SetOptimize(20m, 100m, 5m);

		_breakEvenPips = Param(nameof(BreakEvenPips), 9m)
		.SetNotNegative()
		.SetDisplay("Break-even Trigger (pips)", "Profit distance required to protect the position", "Risk Management")
		
		.SetOptimize(5m, 20m, 1m);

		_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
		.SetNotNegative()
		.SetDisplay("Trailing Stop (pips)", "Distance for the trailing stop after the position moves in profit", "Risk Management")
		
		.SetOptimize(5m, 30m, 1m);

		_takeProfitPips = Param(nameof(TakeProfitPips), 120m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Optional fixed profit target", "Risk Management")
		
		.SetOptimize(40m, 200m, 10m);

		_tradingStartHour = Param(nameof(TradingStartHour), 7)
		.SetRange(0, 23)
		.SetDisplay("Start Hour", "First trading hour (inclusive)", "Scheduling");

		_tradingEndHour = Param(nameof(TradingEndHour), 19)
		.SetRange(1, 24)
		.SetDisplay("End Hour", "Last trading hour (exclusive)", "Scheduling");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for signal generation", "General");
	}

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

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

		_ichimoku = null!;
		_lwma = null!;

		_previousClose = null;
		_previousMa = null;
		_previousPrevMa = null;
		_previousKijun = null;
		_previousPrevKijun = null;
		_pendingLongLevel = null;
		_pendingShortLevel = null;

		ResetPositionState();
	}

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

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanBPeriod }
		};

		_lwma = new WeightedMovingAverage
		{
			Length = LwmaPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_ichimoku, _lwma, ProcessCandle)
		.Start();

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

		// protection handled manually via SL/TP/trailing
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue maValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!maValue.IsFinal)
		return;

		var ichimokuTyped = (IchimokuValue)ichimokuValue;
		if (ichimokuTyped.Kijun is not decimal kijun)
		return;

		var maCurrent = maValue.ToDecimal();

		ManageOpenPosition(candle, maCurrent);

		if (IsFormedAndOnlineAndAllowTrading() && IsWithinTradingHours(candle.OpenTime))
		{
			EvaluateEntrySignals(candle, kijun, maCurrent);
		}

		UpdateHistory(candle, kijun, maCurrent);
	}

	private void ManageOpenPosition(ICandleMessage candle, decimal maCurrent)
	{
		if (Position == 0)
		{
			if (_isLongPosition != null || _entryPrice != null)
			ResetPositionState();
			return;
		}

		var isLong = Position > 0;
		var actualEntry = _entryPrice ?? candle.ClosePrice;
		if (_isLongPosition is null || _isLongPosition.Value != isLong || _entryPrice is null)
		{
			var entry = actualEntry != 0m ? actualEntry : candle.ClosePrice;
			SetupPositionState(isLong, entry);
		}
		else if (actualEntry != 0m && _entryPrice.Value != actualEntry)
		{
			_entryPrice = actualEntry;
			if (_isLongPosition.Value)
			{
				_stopLossPrice = _stopLossDistance > 0m ? _entryPrice - _stopLossDistance : null;
				_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice + _takeProfitDistance : null;
			}
			else
			{
				_stopLossPrice = _stopLossDistance > 0m ? _entryPrice + _stopLossDistance : null;
				_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice - _takeProfitDistance : null;
			}
		}

		if (_entryPrice is not decimal entryPrice)
		return;

		_isLongPosition = isLong;

		if (_previousMa.HasValue && _previousPrevMa.HasValue && _stopLossPrice.HasValue)
		{
			if (isLong && _stopLossPrice.Value < entryPrice && _previousMa.Value < _previousPrevMa.Value)
			{
				ClosePositionAndReset();
				return;
			}

			if (!isLong && _stopLossPrice.Value > entryPrice && _previousMa.Value > _previousPrevMa.Value)
			{
				ClosePositionAndReset();
				return;
			}
		}

		if (isLong)
		{
			ApplyBreakEvenAndTrailingForLong(candle, entryPrice);

			if (CheckStopLossHit(candle.LowPrice, _stopLossPrice))
			{
				ClosePositionAndReset();
				return;
			}

			if (CheckTakeProfitHit(candle.HighPrice, _takeProfitPrice))
			{
				ClosePositionAndReset();
				return;
			}
		}
		else
		{
			ApplyBreakEvenAndTrailingForShort(candle, entryPrice);

			if (CheckStopLossHitForShort(candle.HighPrice, _stopLossPrice))
			{
				ClosePositionAndReset();
				return;
			}

			if (CheckTakeProfitHitForShort(candle.LowPrice, _takeProfitPrice))
			{
				ClosePositionAndReset();
				return;
			}
		}
	}

	private void ApplyBreakEvenAndTrailingForLong(ICandleMessage candle, decimal entryPrice)
	{
		if (!_breakEvenApplied && _breakEvenDistance > 0m)
		{
			if (candle.ClosePrice - entryPrice >= _breakEvenDistance)
			{
				var newStop = entryPrice + (_breakEvenStep > 0m ? _breakEvenStep : 0m);
				if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
				_stopLossPrice = newStop;
				_breakEvenApplied = true;
			}
		}

		if (_trailingDistance > 0m && candle.ClosePrice - entryPrice >= _trailingDistance)
		{
			var newStop = candle.ClosePrice - _trailingDistance;
			if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
			_stopLossPrice = newStop;
		}
	}

	private void ApplyBreakEvenAndTrailingForShort(ICandleMessage candle, decimal entryPrice)
	{
		if (!_breakEvenApplied && _breakEvenDistance > 0m)
		{
			if (entryPrice - candle.ClosePrice >= _breakEvenDistance)
			{
				var newStop = entryPrice - (_breakEvenStep > 0m ? _breakEvenStep : 0m);
				if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
				_stopLossPrice = newStop;
				_breakEvenApplied = true;
			}
		}

		if (_trailingDistance > 0m && entryPrice - candle.ClosePrice >= _trailingDistance)
		{
			var newStop = candle.ClosePrice + _trailingDistance;
			if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
			_stopLossPrice = newStop;
		}
	}

	private static bool CheckStopLossHit(decimal lowPrice, decimal? stopPrice)
	{
		return stopPrice.HasValue && lowPrice <= stopPrice.Value;
	}

	private static bool CheckTakeProfitHit(decimal highPrice, decimal? takeProfitPrice)
	{
		return takeProfitPrice.HasValue && highPrice >= takeProfitPrice.Value;
	}

	private static bool CheckStopLossHitForShort(decimal highPrice, decimal? stopPrice)
	{
		return stopPrice.HasValue && highPrice >= stopPrice.Value;
	}

	private static bool CheckTakeProfitHitForShort(decimal lowPrice, decimal? takeProfitPrice)
	{
		return takeProfitPrice.HasValue && lowPrice <= takeProfitPrice.Value;
	}

	private void EvaluateEntrySignals(ICandleMessage candle, decimal kijun, decimal maCurrent)
	{
		if (_previousClose is not decimal previousClose ||
		_previousMa is not decimal previousMa ||
		_previousKijun is not decimal previousKijun)
		{
			return;
		}

		var maTrendUp = maCurrent > previousMa;
		var maTrendDown = maCurrent < previousMa;
		var filterOffset = ConvertPips(MaFilterPips);

		var priceOpenedBelow = candle.OpenPrice < kijun;
		var priceOpenedAbove = candle.OpenPrice > kijun;
		var priceClosedAbove = candle.ClosePrice > kijun;
		var priceClosedBelow = candle.ClosePrice < kijun;
		var priceTouchedBelow = candle.LowPrice <= kijun;
		var priceTouchedAbove = candle.HighPrice >= kijun;
		var priceWasBelow = previousClose < previousKijun;
		var priceWasAbove = previousClose > previousKijun;
		var kijunNotFalling = !_previousPrevKijun.HasValue || kijun >= _previousPrevKijun.Value;
		var kijunNotRising = !_previousPrevKijun.HasValue || kijun <= _previousPrevKijun.Value;

		if (_pendingLongLevel is null)
		{
			if (priceClosedAbove && (priceOpenedBelow || priceWasBelow || priceTouchedBelow) && kijunNotFalling)
			{
				if (filterOffset <= 0m || maCurrent < kijun - filterOffset)
				{
					_pendingLongLevel = kijun;
					_pendingShortLevel = null;
				}
			}
		}

		if (_pendingShortLevel is null)
		{
			if (priceClosedBelow && (priceOpenedAbove || priceWasAbove || priceTouchedAbove) && kijunNotRising)
			{
				if (filterOffset <= 0m || maCurrent > kijun + filterOffset)
				{
					_pendingShortLevel = kijun;
					_pendingLongLevel = null;
				}
			}
		}

		var volume = Volume + Math.Abs(Position);
		if (volume <= 0m)
		return;

		if (_pendingLongLevel.HasValue && maTrendUp && Position <= 0)
		{
			BuyMarket(volume);
			SetupPositionState(true, candle.ClosePrice);
			_pendingLongLevel = null;
			_pendingShortLevel = null;
			return;
		}

		if (_pendingShortLevel.HasValue && maTrendDown && Position >= 0)
		{
			SellMarket(volume);
			SetupPositionState(false, candle.ClosePrice);
			_pendingLongLevel = null;
			_pendingShortLevel = null;
		}
	}

	private void UpdateHistory(ICandleMessage candle, decimal kijun, decimal maCurrent)
	{
		_previousPrevKijun = _previousKijun;
		_previousKijun = kijun;

		_previousPrevMa = _previousMa;
		_previousMa = maCurrent;

		_previousClose = candle.ClosePrice;
	}

	private bool IsWithinTradingHours(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= TradingStartHour && hour < TradingEndHour;
	}

	private void SetupPositionState(bool isLong, decimal entryPrice)
	{
		_isLongPosition = isLong;
		_entryPrice = entryPrice;
		_breakEvenApplied = false;

		_stopLossDistance = ConvertPips(StopLossPips);
		_takeProfitDistance = ConvertPips(TakeProfitPips);
		_breakEvenDistance = ConvertPips(BreakEvenPips);
		_breakEvenStep = ConvertPips(1m);
		_trailingDistance = ConvertPips(TrailingStopPips);

		_stopLossPrice = _stopLossDistance > 0m ? (isLong ? entryPrice - _stopLossDistance : entryPrice + _stopLossDistance) : null;
		_takeProfitPrice = _takeProfitDistance > 0m ? (isLong ? entryPrice + _takeProfitDistance : entryPrice - _takeProfitDistance) : null;
	}

	private void ClosePositionAndReset()
	{
		if (Position != 0)
			if (Position > 0) SellMarket(Math.Abs(Position)); else if (Position < 0) BuyMarket(Math.Abs(Position));

		ResetPositionState();
	}

	private void ResetPositionState()
	{
		_isLongPosition = null;
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_stopLossDistance = 0m;
		_takeProfitDistance = 0m;
		_breakEvenDistance = 0m;
		_breakEvenStep = 0m;
		_trailingDistance = 0m;
		_breakEvenApplied = false;
	}

	private decimal ConvertPips(decimal value)
	{
		if (value <= 0m)
		return 0m;

		var step = GetPipStep();
		return value * step;
	}

	private decimal GetPipStep()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep is null || priceStep <= 0m)
		return 1m;

		var stepValue = priceStep.Value;
		var decimals = GetDecimalPlaces(stepValue);
		if (decimals == 3 || decimals == 5)
		return stepValue * 10m;

		return stepValue;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		value = Math.Abs(value);
		var decimals = 0;
		while (value != Math.Truncate(value) && decimals < 10)
		{
			value *= 10m;
			decimals++;
		}

		return decimals;
	}
}