DVD 100-50 Cent Strategy
Overview
The DVD 100-50 cent strategy is a contrarian limit-order system ported from the original MT4 expert advisor. The logic evaluates the market across four timeframes (M1, M30, H1, D1) and scores potential setups before parking buy or sell limit orders around the nearest "100 level" price grid. When the limit order is filled the strategy manages the position with pre-calculated stop-loss and take-profit levels.
Indicators and Data
- RAVI (Range Action Verification Index) on H1 and D1, calculated with SMA(2) and SMA(24) on the open price.
- Raw candle data on M1, M30, and H1 for pattern filters such as spike rejection, consolidation checks, and momentum tests.
- Price grid rounding that snaps the current price to the nearest 100-level using a two-decimal rounding and a configurable 0.1-pip offset.
Entry Logic
- Compute the rounded "Level 100" price by rounding the last M1 close to two decimals and shifting it by
PointFromLevelGoPips(default 50 → 5 pips). - Initialize an internal score (BAL) at 0 and add/subtract points according to:
- Trend filter: add 10 points when H1 RAVI is below zero for long setups or above zero for shorts.
- Hourly spike confirmation: add 7 points when the previous two H1 highs/lows overshoot the grid by
RiseFilterPips. - Structure alignment: add 45 points when the current M1 close crosses back over the level and the last three H1 lows/highs stay above/below the safety buffer (
PointFromLevelGoPips ± 30 * 0.1 pip). - Volatility guards: subtract 50 points if recent M1 highs/lows exceed
HighLevelPips(default 600 → 60 pips) or if fast momentum bursts appear while the D1 RAVI confirms a strong directional regime. - Breakout confirmation: subtract 50 points if the last 15 H1 candles never crossed the
LowLevel2Pipsthreshold. - Consolidation filter: subtract 50 points if the latest eight M30 candles all remain inside the
LowLevelPipsband.
- Place a limit order only when the final score is at least 50 and no other exposure (position or pending order) exists.
Order Placement
- Buy limit: 10 pips below the latest M1 close. Stop-loss is
StopLossPipsbelow the limit price, take-profit isTakeProfitPipsabove it. When the D1 RAVI shows a rising staircase between -1 and +5 over the last four days the take-profit receives an extra 25-pip extension. - Sell limit: 7 pips above the latest M1 close with symmetric stop and target rules. When the D1 RAVI shows a falling staircase between -5 and -1 the target is extended by 25 pips.
- Pending orders automatically expire after
OrderExpiryMinutes(default 20 minutes). When an order is cancelled the stored protective levels are reset.
Position Management
- Once filled, the strategy keeps the stored stop-loss and take-profit values internally and issues market exit orders when price touches either level.
- No trailing stop is applied in the ported version; the original EA disabled the trailing logic by default.
- New trades are blocked while an active position or pending limit order exists.
Money Management
- When
UseMoneyManagementis enabled the lot size mimics the MT4 implementation: it scales byTradeSizePercentof current equity, adjusts for mini accounts, and clamps the result to[0.1, MaxVolume](mini) or[1, MaxVolume](standard). - Disabling money management forces a fixed volume controlled by the
FixedVolumeparameter. - Trading halts when portfolio equity drops below
MarginCutoff.
Parameters
| Name | Description | Default |
|---|---|---|
AccountIsMini |
Use mini-account volume rounding rules | true |
UseMoneyManagement |
Enable adaptive lot sizing | true |
TradeSizePercent |
Equity percentage allocated per trade | 10 |
FixedVolume |
Volume used when money management is off | 0.01 |
MaxVolume |
Maximum allowed trade volume | 4 |
StopLossPips |
Stop-loss distance in pips | 210 |
TakeProfitPips |
Take-profit distance in pips | 18 |
PointFromLevelGoPips |
Base level shift in 0.1 pips | 50 |
RiseFilterPips |
Hourly spike confirmation distance (0.1 pips) | 700 |
HighLevelPips |
One-minute spike rejection threshold (0.1 pips) | 600 |
LowLevelPips |
30-minute consolidation band (0.1 pips) | 250 |
LowLevel2Pips |
Hourly breakout confirmation distance (0.1 pips) | 450 |
MarginCutoff |
Equity floor disabling new trades | 300 |
OrderExpiryMinutes |
Pending order lifetime in minutes | 20 |
Usage Notes
- The conversion relies on finished candles from each timeframe; ensure the historical data stream provides synchronized M1, M30, H1, and D1 candles.
- The protective stop and target are executed with market orders to mirror the MT4 behaviour of attached SL/TP values.
- Because the logic is sensitive to pip size, verify that the instrument's
PriceStepandDecimalsproperties correctly describe the quote format.
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>
/// Mean-reversion limit strategy converted from the MT4 expert advisor "DVD 100-50 cent".
/// </summary>
public class Dvd10050CentStrategy : Strategy
{
private readonly StrategyParam<bool> _accountIsMini;
private readonly StrategyParam<bool> _useMoneyManagement;
private readonly StrategyParam<decimal> _tradeSizePercent;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _pointFromLevelGoPips;
private readonly StrategyParam<decimal> _riseFilterPips;
private readonly StrategyParam<decimal> _highLevelPips;
private readonly StrategyParam<decimal> _lowLevelPips;
private readonly StrategyParam<decimal> _lowLevel2Pips;
private readonly StrategyParam<decimal> _marginCutoff;
private readonly StrategyParam<int> _orderExpiryMinutes;
private readonly StrategyParam<int> _m1HistoryLength;
private readonly StrategyParam<int> _m30HistoryLength;
private readonly StrategyParam<int> _h1HistoryLength;
private SimpleMovingAverage _h1Fast = null!;
private SimpleMovingAverage _h1Slow = null!;
private SimpleMovingAverage _d1Fast = null!;
private SimpleMovingAverage _d1Slow = null!;
private readonly List<ICandleMessage> _m1History = new();
private readonly List<ICandleMessage> _m30History = new();
private readonly List<ICandleMessage> _h1Finished = new();
private ICandleMessage _h1Current;
private decimal? _raviH1;
private decimal? _raviD1Current;
private decimal? _raviD1Prev1;
private decimal? _raviD1Prev2;
private decimal? _raviD1Prev3;
private decimal _pipSize;
private decimal _pointValue;
private DateTimeOffset? _buyOrderExpiry;
private DateTimeOffset? _sellOrderExpiry;
private decimal? _pendingBuyStop;
private decimal? _pendingBuyTake;
private decimal? _pendingSellStop;
private decimal? _pendingSellTake;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
private decimal _previousPosition;
/// <summary>
/// Initializes a new instance of the <see cref="Dvd10050CentStrategy"/> class.
/// </summary>
public Dvd10050CentStrategy()
{
_accountIsMini = Param(nameof(AccountIsMini), true)
.SetDisplay("Mini Account", "Use mini account position sizing", "Risk");
_useMoneyManagement = Param(nameof(UseMoneyManagement), true)
.SetDisplay("Use Money Management", "Enable adaptive lot sizing", "Risk");
_tradeSizePercent = Param(nameof(TradeSizePercent), 10m)
.SetDisplay("Risk Percent", "Percent of equity allocated per trade", "Risk")
.SetRange(0m, 100m)
;
_fixedVolume = Param(nameof(FixedVolume), 0.01m)
.SetDisplay("Fixed Volume", "Volume used when money management is disabled", "Risk")
.SetRange(0.01m, 100m)
;
_maxVolume = Param(nameof(MaxVolume), 4m)
.SetDisplay("Max Volume", "Ceiling for calculated trade volume", "Risk")
.SetRange(0.01m, 100m)
;
_stopLossPips = Param(nameof(StopLossPips), 210m)
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Orders")
.SetRange(0m, 1000m)
;
_takeProfitPips = Param(nameof(TakeProfitPips), 18m)
.SetDisplay("Take Profit (pips)", "Initial profit target distance", "Orders")
.SetRange(0m, 500m)
;
_pointFromLevelGoPips = Param(nameof(PointFromLevelGoPips), 50m)
.SetDisplay("Base Offset (0.1 pips)", "Offset used to build the 100 level grid", "Filters")
.SetRange(0m, 1000m)
;
_riseFilterPips = Param(nameof(RiseFilterPips), 700m)
.SetDisplay("Rise Filter (0.1 pips)", "Distance for hourly spike confirmation", "Filters")
.SetRange(0m, 5000m)
;
_highLevelPips = Param(nameof(HighLevelPips), 600m)
.SetDisplay("High Level (0.1 pips)", "One-minute spike rejection threshold", "Filters")
.SetRange(0m, 5000m)
;
_lowLevelPips = Param(nameof(LowLevelPips), 250m)
.SetDisplay("Low Level (0.1 pips)", "Half-hour consolidation ceiling", "Filters")
.SetRange(0m, 5000m)
;
_lowLevel2Pips = Param(nameof(LowLevel2Pips), 450m)
.SetDisplay("Low Level 2 (0.1 pips)", "Hourly breakout confirmation threshold", "Filters")
.SetRange(0m, 5000m)
;
_marginCutoff = Param(nameof(MarginCutoff), 300m)
.SetDisplay("Margin Cutoff", "Stop trading when equity falls below this level", "Risk")
.SetRange(0m, 1_000_000m)
;
_orderExpiryMinutes = Param(nameof(OrderExpiryMinutes), 20)
.SetDisplay("Order Expiry (minutes)", "Lifetime of pending limit orders", "Orders")
.SetRange(1, 240)
;
_m1HistoryLength = Param(nameof(M1HistoryLength), 64)
.SetDisplay("M1 History Length", "Number of M1 candles retained for analysis", "History")
.SetRange(1, 500);
_m30HistoryLength = Param(nameof(M30HistoryLength), 16)
.SetDisplay("M30 History Length", "Number of M30 candles retained for analysis", "History")
.SetRange(1, 200);
_h1HistoryLength = Param(nameof(H1HistoryLength), 16)
.SetDisplay("H1 History Length", "Number of H1 candles retained for analysis", "History")
.SetRange(1, 200);
}
/// <summary>
/// Gets or sets whether the account uses mini lot sizing.
/// </summary>
public bool AccountIsMini
{
get => _accountIsMini.Value;
set => _accountIsMini.Value = value;
}
/// <summary>
/// Gets or sets whether money management is enabled.
/// </summary>
public bool UseMoneyManagement
{
get => _useMoneyManagement.Value;
set => _useMoneyManagement.Value = value;
}
/// <summary>
/// Gets or sets the risk allocation per trade when money management is enabled.
/// </summary>
public decimal TradeSizePercent
{
get => _tradeSizePercent.Value;
set => _tradeSizePercent.Value = value;
}
/// <summary>
/// Gets or sets the fixed trade volume used when money management is disabled.
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Gets or sets the maximum volume allowed per trade.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Gets or sets the stop loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Gets or sets the take profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Gets or sets the base offset that defines the 100 level grid in 0.1 pip units.
/// </summary>
public decimal PointFromLevelGoPips
{
get => _pointFromLevelGoPips.Value;
set => _pointFromLevelGoPips.Value = value;
}
/// <summary>
/// Gets or sets the spike confirmation distance for hourly candles in 0.1 pip units.
/// </summary>
public decimal RiseFilterPips
{
get => _riseFilterPips.Value;
set => _riseFilterPips.Value = value;
}
/// <summary>
/// Gets or sets the rejection distance for one-minute highs in 0.1 pip units.
/// </summary>
public decimal HighLevelPips
{
get => _highLevelPips.Value;
set => _highLevelPips.Value = value;
}
/// <summary>
/// Gets or sets the consolidation ceiling distance for 30-minute highs in 0.1 pip units.
/// </summary>
public decimal LowLevelPips
{
get => _lowLevelPips.Value;
set => _lowLevelPips.Value = value;
}
/// <summary>
/// Gets or sets the hourly breakout confirmation distance in 0.1 pip units.
/// </summary>
public decimal LowLevel2Pips
{
get => _lowLevel2Pips.Value;
set => _lowLevel2Pips.Value = value;
}
/// <summary>
/// Gets or sets the equity level that disables new trades when reached.
/// </summary>
public decimal MarginCutoff
{
get => _marginCutoff.Value;
set => _marginCutoff.Value = value;
}
/// <summary>
/// Gets or sets the pending order lifetime in minutes.
/// </summary>
public int OrderExpiryMinutes
{
get => _orderExpiryMinutes.Value;
set => _orderExpiryMinutes.Value = value;
}
/// <summary>
/// Number of one-minute candles retained for intraday analysis.
/// </summary>
public int M1HistoryLength
{
get => _m1HistoryLength.Value;
set => _m1HistoryLength.Value = value;
}
/// <summary>
/// Number of thirty-minute candles retained for intraday analysis.
/// </summary>
public int M30HistoryLength
{
get => _m30HistoryLength.Value;
set => _m30HistoryLength.Value = value;
}
/// <summary>
/// Number of hourly candles retained for intraday analysis.
/// </summary>
public int H1HistoryLength
{
get => _h1HistoryLength.Value;
set => _h1HistoryLength.Value = value;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_h1Fast = new SimpleMovingAverage { Length = 2 };
_h1Slow = new SimpleMovingAverage { Length = 24 };
_d1Fast = new SimpleMovingAverage { Length = 2 };
_d1Slow = new SimpleMovingAverage { Length = 24 };
_pipSize = CalculatePipSize();
_pointValue = _pipSize / 10m;
var m1Subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
m1Subscription.Bind(ProcessM1).Start();
var m30Subscription = SubscribeCandles(TimeSpan.FromMinutes(30).TimeFrame());
m30Subscription.Bind(ProcessM30).Start();
var h1Subscription = SubscribeCandles(TimeSpan.FromHours(1).TimeFrame());
h1Subscription.Bind(ProcessH1).Start();
var d1Subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
d1Subscription.Bind(ProcessD1).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_h1Fast = null!;
_h1Slow = null!;
_d1Fast = null!;
_d1Slow = null!;
_m1History.Clear();
_m30History.Clear();
_h1Finished.Clear();
_h1Current = null;
_raviH1 = null;
_raviD1Current = null;
_raviD1Prev1 = null;
_raviD1Prev2 = null;
_raviD1Prev3 = null;
_pipSize = 0m;
_pointValue = 0m;
_buyOrderExpiry = null;
_sellOrderExpiry = null;
_pendingBuyStop = null;
_pendingBuyTake = null;
_pendingSellStop = null;
_pendingSellTake = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
_previousPosition = 0m;
}
private void ProcessM1(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_m1History.Add(candle);
TrimHistory(_m1History, M1HistoryLength);
HandlePositionState(candle);
ManageOrderExpirations(candle.CloseTime);
ManageActivePosition(candle);
if (!_h1Fast.IsFormed || !_h1Slow.IsFormed)
return;
if (HasExposure())
return;
if (!HasSufficientMargin())
return;
var orderPlaced = false;
if (TryCalculateBuyScore(candle, out var buyLevel, out var buyScore) && buyScore >= 0m)
{
orderPlaced = PlaceBuyLimit(candle);
}
if (!orderPlaced && TryCalculateSellScore(candle, out var sellLevel, out var sellScore) && sellScore >= 0m)
{
PlaceSellLimit(candle);
}
}
private void ProcessM30(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_m30History.Add(candle);
TrimHistory(_m30History, M30HistoryLength);
}
private void ProcessH1(ICandleMessage candle)
{
_h1Current = candle;
if (candle.State != CandleStates.Finished)
return;
_h1Finished.Add(candle);
TrimHistory(_h1Finished, H1HistoryLength);
_h1Fast.Process(new DecimalIndicatorValue(_h1Fast, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
_h1Slow.Process(new DecimalIndicatorValue(_h1Slow, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
if (!_h1Fast.IsFormed || !_h1Slow.IsFormed)
return;
var slow = _h1Slow.GetCurrentValue();
if (slow == 0m)
return;
var fast = _h1Fast.GetCurrentValue();
_raviH1 = 100m * (fast - slow) / slow;
}
private void ProcessD1(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_d1Fast.Process(new DecimalIndicatorValue(_d1Fast, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
_d1Slow.Process(new DecimalIndicatorValue(_d1Slow, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
if (!_d1Fast.IsFormed || !_d1Slow.IsFormed)
return;
var slow = _d1Slow.GetCurrentValue();
if (slow == 0m)
return;
var fast = _d1Fast.GetCurrentValue();
var ravi = 100m * (fast - slow) / slow;
_raviD1Prev3 = _raviD1Prev2;
_raviD1Prev2 = _raviD1Prev1;
_raviD1Prev1 = _raviD1Current;
_raviD1Current = ravi;
}
private void HandlePositionState(ICandleMessage candle)
{
var currentPosition = Position;
if (currentPosition > 0m && _previousPosition <= 0m)
{
_longStop = _pendingBuyStop;
_longTake = _pendingBuyTake;
_pendingBuyStop = null;
_pendingBuyTake = null;
_buyOrderExpiry = null;
}
else if (currentPosition < 0m && _previousPosition >= 0m)
{
_shortStop = _pendingSellStop;
_shortTake = _pendingSellTake;
_pendingSellStop = null;
_pendingSellTake = null;
_sellOrderExpiry = null;
}
else if (currentPosition == 0m && _previousPosition != 0m)
{
ResetTradeLevels();
}
_previousPosition = currentPosition;
}
private void ManageOrderExpirations(DateTimeOffset currentTime)
{
if (_buyOrderExpiry is DateTimeOffset buyExpiry)
{
if (!HasActiveLimitOrder(Sides.Buy))
{
_buyOrderExpiry = null;
}
else if (currentTime >= buyExpiry)
{
CancelSideOrders(Sides.Buy);
_buyOrderExpiry = null;
_pendingBuyStop = null;
_pendingBuyTake = null;
}
}
if (_sellOrderExpiry is DateTimeOffset sellExpiry)
{
if (!HasActiveLimitOrder(Sides.Sell))
{
_sellOrderExpiry = null;
}
else if (currentTime >= sellExpiry)
{
CancelSideOrders(Sides.Sell);
_sellOrderExpiry = null;
_pendingSellStop = null;
_pendingSellTake = null;
}
}
}
private void ManageActivePosition(ICandleMessage candle)
{
if (Position > 0m)
{
if (_longStop is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Math.Abs(Position));
ResetTradeLevels();
return;
}
if (_longTake is decimal take && candle.HighPrice >= take)
{
SellMarket(Math.Abs(Position));
ResetTradeLevels();
}
}
else if (Position < 0m)
{
if (_shortStop is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(Math.Abs(Position));
ResetTradeLevels();
return;
}
if (_shortTake is decimal take && candle.LowPrice <= take)
{
BuyMarket(Math.Abs(Position));
ResetTradeLevels();
}
}
}
private bool TryCalculateBuyScore(ICandleMessage candle, out decimal level100, out decimal score)
{
score = 0m;
level100 = 0m;
if (_raviH1 is not decimal raviH1 || _raviD1Current is not decimal raviD1)
return false;
var previousM1 = GetM1Candle(1);
var h1Low0 = GetH1Low(0);
var h1Low1 = GetH1Low(1);
var h1Low2 = GetH1Low(2);
var h1High1 = GetH1High(1);
var h1High2 = GetH1High(2);
if (previousM1 is null || h1Low0 is null || h1Low1 is null || h1Low2 is null || h1High1 is null || h1High2 is null)
return false;
level100 = Math.Round(candle.ClosePrice, 2, MidpointRounding.AwayFromZero) + PointFromLevelGoPips * _pointValue;
var riseThreshold = level100 + RiseFilterPips * _pointValue;
var baseLow = level100 - PointFromLevelGoPips * _pointValue;
var tolerance = 30m * _pointValue;
if (raviH1 < 0m)
score += 10m;
if (h1High1 > riseThreshold || h1High2 > riseThreshold)
score += 7m;
if (candle.ClosePrice < level100 && previousM1.ClosePrice > level100 &&
h1Low0.Value > baseLow + tolerance && h1Low1.Value > baseLow + tolerance && h1Low2.Value > baseLow)
{
score += 45m;
}
if (CheckM1HighAbove(level100 + HighLevelPips * _pointValue, 12))
score -= 50m;
if (raviD1 < -2m && CheckM1ImpulseForBuy())
score -= 50m;
if (!CheckH1BreakAbove(level100 + LowLevel2Pips * _pointValue))
score -= 50m;
if (CheckM30CompressionAbove(level100 + LowLevelPips * _pointValue))
score -= 50m;
return true;
}
private bool TryCalculateSellScore(ICandleMessage candle, out decimal level100, out decimal score)
{
score = 0m;
level100 = 0m;
if (_raviH1 is not decimal raviH1 || _raviD1Current is not decimal raviD1)
return false;
var previousM1 = GetM1Candle(1);
var h1High0 = GetH1High(0);
var h1High1 = GetH1High(1);
var h1High2 = GetH1High(2);
var h1Low1 = GetH1Low(1);
var h1Low2 = GetH1Low(2);
if (previousM1 is null || h1High0 is null || h1High1 is null || h1High2 is null || h1Low1 is null || h1Low2 is null)
return false;
level100 = Math.Round(candle.ClosePrice, 2, MidpointRounding.AwayFromZero) - PointFromLevelGoPips * _pointValue;
var fallThreshold = level100 - RiseFilterPips * _pointValue;
var baseHigh = level100 + PointFromLevelGoPips * _pointValue;
var tolerance = 30m * _pointValue;
if (raviH1 > 0m)
score += 10m;
if (h1Low1 < fallThreshold || h1Low2 < fallThreshold)
score += 7m;
if (candle.ClosePrice > level100 && previousM1.ClosePrice < level100 &&
h1High0.Value < baseHigh - tolerance && h1High1.Value < baseHigh - tolerance && h1High2.Value < baseHigh)
{
score += 45m;
}
if (CheckM1LowBelow(level100 - HighLevelPips * _pointValue, 12))
score -= 50m;
if (raviD1 > 2m && CheckM1ImpulseForSell())
score -= 50m;
if (!CheckH1BreakBelow(level100 - LowLevel2Pips * _pointValue))
score -= 50m;
if (CheckM30CompressionBelow(level100 - LowLevelPips * _pointValue))
score -= 50m;
return true;
}
private bool PlaceBuyLimit(ICandleMessage candle)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
return false;
var entryPrice = Math.Max(candle.ClosePrice - 10m * _pipSize, 0m);
var stopPrice = entryPrice - StopLossPips * _pipSize;
var takePrice = entryPrice + TakeProfitPips * _pipSize;
if (_raviD1Current is decimal ravi && _raviD1Prev1 is decimal prev1 && _raviD1Prev2 is decimal prev2 && _raviD1Prev3 is decimal prev3)
{
if (ravi > 1m && ravi < 5m && prev1 < ravi && prev2 < prev1 && prev3 < prev2)
{
takePrice += 25m * _pipSize;
}
}
BuyLimit(price: entryPrice, volume: volume);
_buyOrderExpiry = candle.CloseTime + TimeSpan.FromMinutes(OrderExpiryMinutes);
_pendingBuyStop = stopPrice;
_pendingBuyTake = takePrice;
return true;
}
private bool PlaceSellLimit(ICandleMessage candle)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
return false;
var entryPrice = candle.ClosePrice + 7m * _pipSize;
var stopPrice = entryPrice + StopLossPips * _pipSize;
var takePrice = entryPrice - TakeProfitPips * _pipSize;
if (_raviD1Current is decimal ravi && _raviD1Prev1 is decimal prev1 && _raviD1Prev2 is decimal prev2 && _raviD1Prev3 is decimal prev3)
{
if (ravi < -1m && ravi > -5m && prev1 > ravi && prev2 > prev1 && prev3 > prev2)
{
takePrice -= 25m * _pipSize;
}
}
SellLimit(price: entryPrice, volume: volume);
_sellOrderExpiry = candle.CloseTime + TimeSpan.FromMinutes(OrderExpiryMinutes);
_pendingSellStop = stopPrice;
_pendingSellTake = takePrice;
return true;
}
private bool CheckM1HighAbove(decimal threshold, int candles)
{
for (var i = 0; i < candles; i++)
{
var candle = GetM1Candle(i);
if (candle is null)
break;
if (candle.HighPrice > threshold)
return true;
}
return false;
}
private bool CheckM1LowBelow(decimal threshold, int candles)
{
for (var i = 0; i < candles; i++)
{
var candle = GetM1Candle(i);
if (candle is null)
break;
if (candle.LowPrice < threshold)
return true;
}
return false;
}
private bool CheckM1ImpulseForBuy()
{
for (var shift = 0; shift <= 30; shift++)
{
var current = GetM1Candle(shift);
var future = GetM1Candle(shift + 3);
if (current is null || future is null)
break;
if (future.HighPrice - current.LowPrice > 300m * _pointValue && future.OpenPrice > current.ClosePrice)
return true;
}
return false;
}
private bool CheckM1ImpulseForSell()
{
for (var shift = 0; shift <= 30; shift++)
{
var current = GetM1Candle(shift);
var future = GetM1Candle(shift + 3);
if (current is null || future is null)
break;
if (current.HighPrice - future.LowPrice > 300m * _pointValue && current.ClosePrice > future.OpenPrice)
return true;
}
return false;
}
private bool CheckH1BreakAbove(decimal threshold)
{
for (var shift = 0; shift <= 14; shift++)
{
var high = GetH1High(shift);
if (high is null)
break;
if (high.Value > threshold)
return true;
}
return false;
}
private bool CheckH1BreakBelow(decimal threshold)
{
for (var shift = 0; shift <= 14; shift++)
{
var low = GetH1Low(shift);
if (low is null)
break;
if (low.Value < threshold)
return true;
}
return false;
}
private bool CheckM30CompressionAbove(decimal threshold)
{
for (var shift = 0; shift <= 7; shift++)
{
var candle = GetM30Candle(shift);
if (candle is null)
return false;
if (candle.HighPrice >= threshold)
return false;
}
return true;
}
private bool CheckM30CompressionBelow(decimal threshold)
{
for (var shift = 0; shift <= 7; shift++)
{
var candle = GetM30Candle(shift);
if (candle is null)
return false;
if (candle.LowPrice <= threshold)
return false;
}
return true;
}
private ICandleMessage GetM1Candle(int shift)
{
var index = _m1History.Count - 1 - shift;
return index >= 0 && index < _m1History.Count ? _m1History[index] : null;
}
private ICandleMessage GetM30Candle(int shift)
{
var index = _m30History.Count - 1 - shift;
return index >= 0 && index < _m30History.Count ? _m30History[index] : null;
}
private decimal? GetH1High(int shift)
{
if (shift == 0)
return _h1Current?.HighPrice;
var index = _h1Finished.Count - shift;
return index >= 0 && index < _h1Finished.Count ? _h1Finished[index].HighPrice : null;
}
private decimal? GetH1Low(int shift)
{
if (shift == 0)
return _h1Current?.LowPrice;
var index = _h1Finished.Count - shift;
return index >= 0 && index < _h1Finished.Count ? _h1Finished[index].LowPrice : null;
}
private void CancelSideOrders(Sides side)
{
foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
{
if (order.Type != OrderTypes.Limit || order.Side != side)
continue;
CancelOrder(order);
}
}
private bool HasActiveLimitOrder(Sides side)
{
foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
{
if (order.Type == OrderTypes.Limit && order.Side == side)
return true;
}
return false;
}
private bool HasExposure()
{
if (Position != 0m)
return true;
foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
{
if (order.Type == OrderTypes.Limit)
return true;
}
return false;
}
private bool HasSufficientMargin()
{
if (MarginCutoff <= 0m)
return true;
var portfolio = Portfolio;
if (portfolio is null)
return true;
var equity = portfolio.CurrentValue ?? 0m;
if (equity <= 0m)
equity = portfolio.BeginValue ?? 0m;
return equity >= MarginCutoff;
}
private decimal CalculateOrderVolume()
{
if (!UseMoneyManagement)
return FixedVolume;
var portfolio = Portfolio;
if (portfolio is null)
return FixedVolume;
var equity = portfolio.CurrentValue ?? 0m;
if (equity <= 0m)
equity = portfolio.BeginValue ?? 0m;
if (equity <= 0m)
return FixedVolume;
var lot = Math.Floor(equity * TradeSizePercent / 1000m) / 100m;
if (AccountIsMini)
{
lot = Math.Floor(lot * 100m) / 100m;
if (lot < 0.1m)
lot = 0.1m;
}
else
{
if (lot < 1m)
lot = 1m;
}
if (lot > MaxVolume)
lot = MaxVolume;
return lot;
}
private void ResetTradeLevels()
{
_pendingBuyStop = null;
_pendingBuyTake = null;
_pendingSellStop = null;
_pendingSellTake = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
private static void TrimHistory(ICollection<ICandleMessage> history, int maxCount)
{
while (history.Count > maxCount)
{
if (history is List<ICandleMessage> list)
list.RemoveAt(0);
else
break;
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0.0001m;
var decimals = Security?.Decimals ?? 0;
var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
var pip = step * adjust;
return pip == 0m ? 0.0001m : pip;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
from indicator_extensions import *
class dvd10050_cent_strategy(Strategy):
def __init__(self):
super(dvd10050_cent_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Monitoring timeframe.", "General")
self._stop_loss_pips = self.Param("StopLossPips", 210.0) \
.SetDisplay("Stop Loss (pips)", "Protective stop distance.", "Orders")
self._take_profit_pips = self.Param("TakeProfitPips", 18.0) \
.SetDisplay("Take Profit (pips)", "Profit target distance.", "Orders")
self._point_offset = self.Param("PointFromLevelGoPips", 50.0) \
.SetDisplay("Base Offset (0.1 pips)", "Offset for 100 level grid.", "Filters")
self._rise_filter = self.Param("RiseFilterPips", 700.0) \
.SetDisplay("Rise Filter (0.1 pips)", "Hourly spike confirmation.", "Filters")
self._high_level = self.Param("HighLevelPips", 600.0) \
.SetDisplay("High Level (0.1 pips)", "One-minute spike rejection.", "Filters")
self._low_level = self.Param("LowLevelPips", 250.0) \
.SetDisplay("Low Level (0.1 pips)", "Half-hour consolidation ceiling.", "Filters")
self._low_level2 = self.Param("LowLevel2Pips", 450.0) \
.SetDisplay("Low Level 2 (0.1 pips)", "Hourly breakout confirmation.", "Filters")
self._m1_hist_len = self.Param("M1HistoryLength", 64) \
.SetDisplay("M1 History Length", "Number of M1 candles retained.", "History")
self._h1_hist_len = self.Param("H1HistoryLength", 16) \
.SetDisplay("H1 History Length", "Number of H1 candles retained.", "History")
self._m30_hist_len = self.Param("M30HistoryLength", 16) \
.SetDisplay("M30 History Length", "Number of M30 candles retained.", "History")
self._m1_history = []
self._m30_history = []
self._h1_finished = []
self._h1_fast = None
self._h1_slow = None
self._ravi_h1 = None
self._pip_size = 0.0001
self._point_value = 0.00001
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def PointFromLevelGoPips(self):
return float(self._point_offset.Value)
@property
def RiseFilterPips(self):
return float(self._rise_filter.Value)
@property
def HighLevelPips(self):
return float(self._high_level.Value)
@property
def LowLevelPips(self):
return float(self._low_level.Value)
@property
def LowLevel2Pips(self):
return float(self._low_level2.Value)
@property
def M1HistoryLength(self):
return self._m1_hist_len.Value
@property
def H1HistoryLength(self):
return self._h1_hist_len.Value
@property
def M30HistoryLength(self):
return self._m30_hist_len.Value
def OnStarted2(self, time):
super(dvd10050_cent_strategy, self).OnStarted2(time)
self._m1_history = []
self._m30_history = []
self._h1_finished = []
self._ravi_h1 = None
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
sec = self.Security
step = 0.0001
if sec is not None:
ps = sec.PriceStep
if ps is not None and float(ps) > 0:
step = float(ps)
self._pip_size = step
self._point_value = step / 10.0
self._h1_fast = SimpleMovingAverage()
self._h1_fast.Length = 2
self._h1_slow = SimpleMovingAverage()
self._h1_slow.Length = 24
sub_m1 = self.SubscribeCandles(self.CandleType)
sub_m1.Bind(self.ProcessM1).Start()
sub_m30 = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(30)))
sub_m30.Bind(self.ProcessM30).Start()
sub_h1 = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromHours(1)))
sub_h1.Bind(self.ProcessH1).Start()
def ProcessM1(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
self._m1_history.append((close, high, low))
while len(self._m1_history) > self.M1HistoryLength:
self._m1_history.pop(0)
# manage position
if self.Position > 0:
if self._long_stop is not None and low <= self._long_stop:
self.SellMarket()
self._reset_levels()
return
if self._long_take is not None and high >= self._long_take:
self.SellMarket()
self._reset_levels()
return
elif self.Position < 0:
if self._short_stop is not None and high >= self._short_stop:
self.BuyMarket()
self._reset_levels()
return
if self._short_take is not None and low <= self._short_take:
self.BuyMarket()
self._reset_levels()
return
if self._ravi_h1 is None:
return
if self.Position != 0:
return
# try buy
buy_score = self._calc_buy_score(close, high, low)
if buy_score is not None and buy_score >= 0:
entry = close
sl = entry - self.StopLossPips * self._pip_size
tp = entry + self.TakeProfitPips * self._pip_size
self._entry_price = entry
self._long_stop = sl
self._long_take = tp
self._short_stop = None
self._short_take = None
self.BuyMarket()
return
# try sell
sell_score = self._calc_sell_score(close, high, low)
if sell_score is not None and sell_score >= 0:
entry = close
sl = entry + self.StopLossPips * self._pip_size
tp = entry - self.TakeProfitPips * self._pip_size
self._entry_price = entry
self._short_stop = sl
self._short_take = tp
self._long_stop = None
self._long_take = None
self.SellMarket()
def ProcessM30(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
self._m30_history.append((high, low))
while len(self._m30_history) > self.M30HistoryLength:
self._m30_history.pop(0)
def ProcessH1(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
op = float(candle.OpenPrice)
self._h1_finished.append((high, low, op))
while len(self._h1_finished) > self.H1HistoryLength:
self._h1_finished.pop(0)
process_float(self._h1_fast, Decimal(op), candle.CloseTime, True)
process_float(self._h1_slow, Decimal(op), candle.CloseTime, True)
if not self._h1_fast.IsFormed or not self._h1_slow.IsFormed:
return
from StockSharp.Algo.Indicators import IndicatorHelper
slow_v = float(IndicatorHelper.GetCurrentValue(self._h1_slow))
if slow_v == 0:
return
fast_v = float(IndicatorHelper.GetCurrentValue(self._h1_fast))
self._ravi_h1 = 100.0 * (fast_v - slow_v) / slow_v
def _get_m1(self, shift):
idx = len(self._m1_history) - 1 - shift
if 0 <= idx < len(self._m1_history):
return self._m1_history[idx]
return None
def _get_h1_high(self, shift):
idx = len(self._h1_finished) - 1 - shift
if 0 <= idx < len(self._h1_finished):
return self._h1_finished[idx][0]
return None
def _get_h1_low(self, shift):
idx = len(self._h1_finished) - 1 - shift
if 0 <= idx < len(self._h1_finished):
return self._h1_finished[idx][1]
return None
def _get_m30(self, shift):
idx = len(self._m30_history) - 1 - shift
if 0 <= idx < len(self._m30_history):
return self._m30_history[idx]
return None
def _check_m1_high_above(self, threshold, count):
for i in range(count):
m = self._get_m1(i)
if m is None:
break
if m[1] > threshold:
return True
return False
def _check_m1_low_below(self, threshold, count):
for i in range(count):
m = self._get_m1(i)
if m is None:
break
if m[2] < threshold:
return True
return False
def _check_h1_break_above(self, threshold):
for i in range(15):
h = self._get_h1_high(i)
if h is None:
break
if h > threshold:
return True
return False
def _check_h1_break_below(self, threshold):
for i in range(15):
lo = self._get_h1_low(i)
if lo is None:
break
if lo < threshold:
return True
return False
def _check_m30_compression_above(self, threshold):
for i in range(8):
m = self._get_m30(i)
if m is None:
return False
if m[0] >= threshold:
return False
return True
def _check_m30_compression_below(self, threshold):
for i in range(8):
m = self._get_m30(i)
if m is None:
return False
if m[1] <= threshold:
return False
return True
def _calc_buy_score(self, close, high, low):
if self._ravi_h1 is None:
return None
prev_m1 = self._get_m1(1)
h1_low0 = self._get_h1_low(0)
h1_low1 = self._get_h1_low(1)
h1_low2 = self._get_h1_low(2)
h1_high1 = self._get_h1_high(1)
h1_high2 = self._get_h1_high(2)
if prev_m1 is None or h1_low0 is None or h1_low1 is None or h1_low2 is None:
return None
if h1_high1 is None or h1_high2 is None:
return None
pv = self._point_value
level100 = round(close, 2) + self.PointFromLevelGoPips * pv
rise_thr = level100 + self.RiseFilterPips * pv
base_low = level100 - self.PointFromLevelGoPips * pv
tol = 30.0 * pv
score = 0.0
if self._ravi_h1 < 0:
score += 10.0
if h1_high1 > rise_thr or h1_high2 > rise_thr:
score += 7.0
if close < level100 and prev_m1[0] > level100 and h1_low0 > base_low + tol and h1_low1 > base_low + tol and h1_low2 > base_low:
score += 45.0
if self._check_m1_high_above(level100 + self.HighLevelPips * pv, 12):
score -= 50.0
if not self._check_h1_break_above(level100 + self.LowLevel2Pips * pv):
score -= 50.0
if self._check_m30_compression_above(level100 + self.LowLevelPips * pv):
score -= 50.0
return score
def _calc_sell_score(self, close, high, low):
if self._ravi_h1 is None:
return None
prev_m1 = self._get_m1(1)
h1_high0 = self._get_h1_high(0)
h1_high1 = self._get_h1_high(1)
h1_high2 = self._get_h1_high(2)
h1_low1 = self._get_h1_low(1)
h1_low2 = self._get_h1_low(2)
if prev_m1 is None or h1_high0 is None or h1_high1 is None or h1_high2 is None:
return None
if h1_low1 is None or h1_low2 is None:
return None
pv = self._point_value
level100 = round(close, 2) - self.PointFromLevelGoPips * pv
fall_thr = level100 - self.RiseFilterPips * pv
base_high = level100 + self.PointFromLevelGoPips * pv
tol = 30.0 * pv
score = 0.0
if self._ravi_h1 > 0:
score += 10.0
if h1_low1 < fall_thr or h1_low2 < fall_thr:
score += 7.0
if close > level100 and prev_m1[0] < level100 and h1_high0 < base_high - tol and h1_high1 < base_high - tol and h1_high2 < base_high:
score += 45.0
if self._check_m1_low_below(level100 - self.HighLevelPips * pv, 12):
score -= 50.0
if not self._check_h1_break_below(level100 - self.LowLevel2Pips * pv):
score -= 50.0
if self._check_m30_compression_below(level100 - self.LowLevelPips * pv):
score -= 50.0
return score
def _reset_levels(self):
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._entry_price = 0.0
def OnReseted(self):
super(dvd10050_cent_strategy, self).OnReseted()
self._m1_history = []
self._m30_history = []
self._h1_finished = []
self._ravi_h1 = None
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def CreateClone(self):
return dvd10050_cent_strategy()