DVD 100-50 セント戦略
概要
DVD 100-50 セント戦略は、オリジナルの MT4 エキスパートアドバイザーから移植された逆張り指値注文システムです。このロジックは、4 つの時間枠 (M1、M30、H1、D1) にわたって市場を評価し、最も近い「100 レベル」の価格グリッド付近で買いまたは売りの指値注文を保留する前に、潜在的な設定をスコア付けします。指値注文が約定されると、戦略は事前に計算されたストップロスとテイクプロフィットのレベルでポジションを管理します。
指標とデータ
- H1 と D1 の RAVI (レンジ アクション検証指数)。始値の SMA(2) と SMA(24) で計算されます。
- スパイク除去、統合チェック、運動量テストなどのパターン フィルター用の M1、M30、および H1 に関する 生のローソク足データ。
- 価格グリッドの四捨五入。小数点第 2 位の四捨五入と設定可能な 0.1 ピップのオフセットを使用して、現在の価格を最も近い 100 レベルにスナップします。
エントリーロジック
- 最後の M1 を小数点第 2 位近くで四捨五入し、
PointFromLevelGoPipsだけシフトすることで、丸められた「レベル 100」価格を計算します (デフォルトは 50 → 5 ピップス)。 - 内部スコア (BAL) を 0 に初期化し、次に従ってポイントを加算/減算します。
- トレンド フィルター: H1 RAVI がロング セットアップの場合はゼロ未満、またはショートの場合はゼロを上回る場合、10 ポイントを追加します。
- 1 時間ごとのスパイク確認: 上半期の過去 2 つの高値/安値がグリッドを
RiseFilterPips超えた場合、7 ポイントを追加します。 - ストラクチャーアライメント: 現在の M1 終値がレベルを再びクロスオーバーし、最後の 3 つの H1 安値/高値が安全バッファー (
PointFromLevelGoPips ± 30 * 0.1 pip) の上または下に留まる場合、45 ポイントを追加します。 - ボラティリティ ガード: 最近の M1 高値/安値が
HighLevelPips(デフォルト 600 → 60 ピップス) を超えた場合、または D1 RAVI が強い方向性を確認している間に速い勢いのバーストが現れた場合は、50 ポイントを減算します。 - ブレイクアウトの確認: 最後の 15 本の上半期ローソク足が
LowLevel2Pipsのしきい値を超えなかった場合は、50 ポイントを減算します。 - 統合フィルター: 最新の 8 つの M30 ローソク足がすべて
LowLevelPipsバンド内に残っている場合、50 ポイントを減算します。
- 最終スコアが少なくとも 50 で、他のエクスポージャー (ポジションまたは未決注文) が存在しない場合にのみ、指値注文を出します。
注文の発注
- 買い制限: 最新の M1 終値より 10 ピップス下。ストップロスは指値価格より
StopLossPips低く、テイクプロフィットはそれよりTakeProfitPips上です。 D1 RAVI が過去 4 日間で -1 から +5 の間の上昇階段を示した場合、テイクプロフィットは追加の 25 ピップスのエクステンションを受け取ります。 - 売りリミット: 対称的なストップとターゲットのルールにより、最新の M1 終値より 7 ピップス上。 D1 RAVI が -5 と -1 の間で下降階段を示している場合、ターゲットは 25 ピップス延長されます。
- 保留中の注文は、
OrderExpiryMinutes(デフォルトは 20 分) 後に自動的に期限切れになります。注文がキャンセルされると、保存されている保護レベルはリセットされます。
ポジション管理
- 満たされると、この戦略は保存されたストップロスとテイクプロフィットの値を内部的に保持し、価格がいずれかのレベルに達したときに市場撤退注文を発行します。
- 移植されたバージョンではトレーリングストップは適用されません。元の EA はデフォルトで後続ロジックを無効にしていました。
- アクティブなポジションまたは保留中の指値注文が存在する間は、新しい取引はブロックされます。
資金管理
UseMoneyManagementが有効な場合、ロット サイズは MT4 実装を模倣します。現在の資産のTradeSizePercentによってスケールされ、ミニ口座用に調整され、結果が[0.1, MaxVolume](ミニ) または[1, MaxVolume](スタンダード) にクランプされます。- 資金管理を無効にすると、
FixedVolumeパラメータによって制御される固定量が強制されます。 - ポートフォリオの資本が
MarginCutoffを下回ると取引が停止します。
パラメーター
| 名前 | 説明 | デフォルト |
|---|---|---|
AccountIsMini |
ミニアカウントのボリューム四捨五入ルールを使用する | true |
UseMoneyManagement |
適応的なロットサイジングを有効にする | true |
TradeSizePercent |
取引ごとに割り当てられる株式の割合 | 10 |
FixedVolume |
資金管理がオフのときに使用されるボリューム | 0.01 |
MaxVolume |
最大許容取引量 | 4 |
StopLossPips |
ストップロス距離 (pips) | 210 |
TakeProfitPips |
利益確定距離 (pips) | 18 |
PointFromLevelGoPips |
ベースレベルのシフト (0.1 ピップ単位) | 50 |
RiseFilterPips |
時間当たりのスパイク確認距離 (0.1 pips) | 700 |
HighLevelPips |
1 分間のスパイク拒否閾値 (0.1 pips) | 600 |
LowLevelPips |
30 分間の保ち合いバンド (0.1 ピップス) | 250 |
LowLevel2Pips |
1時間ごとのブレイクアウト確認距離(0.1pips) | 450 |
MarginCutoff |
新規取引を無効にする株価下限 | 300 |
OrderExpiryMinutes |
未決注文の存続期間 (分単位) | 20 |
使用上の注意
- 変換は各タイムフレームの完成したローソク足に依存します。履歴データ ストリームが同期された M1、M30、H1、および D1 ローソク足を提供していることを確認します。
- 保護的なストップとターゲットは、添付された SL/TP 値の MT4 の動作を反映するために成行注文で実行されます。
- ロジックはピップサイズに影響されるため、商品の
PriceStepプロパティとDecimalsプロパティが相場フォーマットを正しく記述していることを確認してください。
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()