SilverTrend V3戦略 (C#)
概要
SilverTrend V3戦略は、MetaTrader 5のエキスパートアドバイザー「SilverTrend v3」を起源とするモメンタムフォローシステムです。StockSharpへのポートは、高レベル戦略APIに適合させながら元のロジックを再現しています。中心的なアイデアは、SilverTrendチャネル計算を使って強気または弱気のモメンタムを検出し、J_TPOマーケットプロファイルオシレーターで確認し、保護ストップ、トレーリングロジック、および金曜日セッションフィルターで結果ポジションを管理することです。
シグナルエンジン
SilverTrendの方向
- 動的サポート(
smin)とレジスタンス(smax)を計算するために、9バーのスムージングパラメーターを持つ350バーのローリングウィンドウを使用します。 - 現在の終値が
sminを下回ると、システムは弱気レジームにフラグを立て、smaxを上回る終値は強気レジームに切り替わります。 - 計算は最古のバーから最新のバーへ反復し、元のMQLコードの再帰的な性質を再現します。
- 動的サポート(
J_TPOの確認
- 価格が短期分布内にどのように集中するかを測定する、元の14期間J_TPOオシレーターを実装しています。
- オシレーターが正の場合のみロングエントリーを許可し、負の場合のみショートエントリーを許可することで、弱いモメンタムシフトをフィルタリングします。
シグナル変化の検出
- 新たに計算されたSilverTrendの方向が前回の値と異なる場合にのみトレードが開始され、戦略がノイズではなく真のレジームシフトに反応することを保証します。
トレード管理
- 成行エントリー – 戦略は設定された
Volumeでトレードします。反対のポジションが開いている場合、1つの成行注文で決済して反転します。 - 初期ストップロス – オプション。エントリー価格に対する価格ステップで定義されます(インストゥルメントの
PriceStepで変換)。 - テイクプロフィット – オプション。価格ステップで定義され、元の注文変更動作を模倣するためにローソク足の高安値に対して評価されます。
- トレーリングストップ – 設定されたトレーリング距離だけ価格が有利方向に動いた後にアクティブになります。ロングポジションではストップが上方に引き上げられ、ショートでは下方に引き下げられ、MetaTraderのロジックと一致します。
- 反対シグナルでの決済 – 前のレジームが反対方向を指している場合、既存のポジションは次のローソク足の終値で清算されます。
- 金曜日トレードブロック – 週末ギャップを避けるために、元のEAと同様に、金曜日の指定時刻以降は新規ポジションをスキップします。
パラメーター
| 名前 | デフォルト値 | 説明 |
|---|---|---|
TrailingStopPoints |
50 | 価格ステップで測定したトレーリングストップ距離。トレーリングを無効にするにはゼロに設定。 |
TakeProfitPoints |
50 | 価格ステップでのテイクプロフィット距離。ゼロはターゲットを無効にします。 |
InitialStopLossPoints |
0 | 価格ステップでの初期保護ストップ。ゼロは初期ストップなしでポジションを残します。 |
FridayCutoffHour |
16 | 金曜日に新規エントリーが許可されない取引所時刻。終日取引を許可するには0を使用。 |
CandleType |
1時間足 | インジケーターに供給するデータシリーズ。サポートされている任意の時間軸を使用できます。 |
Volume |
1ロット | 各ポジションのトレードサイズ(StockSharpのVolumeプロパティ)。 |
すべての距離は実行時にPriceStepで乗算され、銘柄のティックサイズ(3/5桁のフォレックスシンボルを含む)に自動的に適合します。
データと環境要件
- SilverTrendとJ_TPOの両方のバッファが完全に形成されるよう、ライブシグナルが生成される前に少なくとも360本の完成したローソク足が必要です。
SubscribeCandlesによる単一インストゥルメントのトレーディング向けに設計されています。GetWorkingSecuritiesオーバーライドにより、戦略は設定された銘柄と時間軸のみを購読することが保証されます。- 起動時に一度
StartProtection()を使用して、標準のStockSharpポジション保護サービスを有効にします。
使用上の注意
- アルゴリズムは主要なフォレックスペアや流動性の高い先物などのトレンドインストゥルメントを想定しています。市場のボラティリティに合わせて時間軸を調整してください。
- SilverTrendの計算は再帰的なため、履歴ローソク足が不十分な状態で戦略を再起動すると、十分なデータが収集されるまでシグナル形成が遅延します。
- 高レベルAPI実装はローソク足の高安値を使用して注文管理(ストップロス、テイクプロフィット、トレーリング)をシミュレートします。ライブトレーディングでは、インフラが必要とする場合、実際のストップ/リミット注文とロジックを組み合わせることを検討してください。
- ポートは内部状態(
_previousSignal、_entryPrice、トレーリングストップ)を完了したローソク足ごとに正確に1回格納し、元のEAの「1バーあたり1トレード」の動作と一致します。
変換の詳細
- ネストされた配列J_TPOアルゴリズムを含む
SilverTrend v3.mq5の数学的ルーティンを忠実に再現しています。 - C#のベストプラクティスを適用:パラメーターは
StrategyParam<T>を通じて公開され、すべてのコメントは英語で、インデントはリポジトリのガイドラインに従いタブを使用しています。 - タスク要件に従い、このリリースには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>
/// SilverTrend v3 momentum strategy ported from MetaTrader 5.
/// </summary>
public class SilverTrendV3Strategy : Strategy
{
private readonly StrategyParam<int> _countBars;
private readonly StrategyParam<int> _ssp;
private readonly StrategyParam<int> _jtpoLength;
private readonly StrategyParam<int> _historyCapacity;
private readonly StrategyParam<int> _risk;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _initialStopLossPoints;
private readonly StrategyParam<int> _fridayCutoffHour;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closeHistory = new();
private readonly List<decimal> _highHistory = new();
private readonly List<decimal> _lowHistory = new();
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private decimal _entryPrice;
private int _previousSignal;
private decimal _pointValue;
/// <summary>
/// Trailing stop distance expressed in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initial stop loss distance expressed in price steps.
/// </summary>
public decimal InitialStopLossPoints
{
get => _initialStopLossPoints.Value;
set => _initialStopLossPoints.Value = value;
}
/// <summary>
/// Hour after which no new trades are allowed on Friday (exchange time).
/// </summary>
public int FridayCutoffHour
{
get => _fridayCutoffHour.Value;
set => _fridayCutoffHour.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of bars used in the indicator history.
/// </summary>
public int CountBars
{
get => _countBars.Value;
set => _countBars.Value = value;
}
/// <summary>
/// Sliding window length for the signal filter.
/// </summary>
public int Ssp
{
get => _ssp.Value;
set => _ssp.Value = value;
}
/// <summary>
/// Length used when smoothing JTPO indicator.
/// </summary>
public int JtpoLength
{
get => _jtpoLength.Value;
set => _jtpoLength.Value = value;
}
/// <summary>
/// Maximum number of candles stored in history.
/// </summary>
public int HistoryCapacity
{
get => _historyCapacity.Value;
set => _historyCapacity.Value = value;
}
/// <summary>
/// Risk coefficient used in signal calculations.
/// </summary>
public int Risk
{
get => _risk.Value;
set => _risk.Value = value;
}
/// <summary>
/// Initialize default parameters.
/// </summary>
public SilverTrendV3Strategy()
{
_countBars = Param(nameof(CountBars), 150)
.SetGreaterThanZero()
.SetDisplay("Count Bars", "Number of candles required before trading", "Indicator");
_ssp = Param(nameof(Ssp), 9)
.SetGreaterThanZero()
.SetDisplay("SSP", "Sliding window length", "Indicator");
_jtpoLength = Param(nameof(JtpoLength), 14)
.SetGreaterThanZero()
.SetDisplay("JTPO Length", "JTPO smoothing length", "Indicator");
_historyCapacity = Param(nameof(HistoryCapacity), 220)
.SetGreaterThanZero()
.SetDisplay("History Capacity", "Maximum stored candles", "Indicator");
_risk = Param(nameof(Risk), 3)
.SetGreaterThanZero()
.SetDisplay("Risk", "Risk coefficient", "Trading");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 50m)
.SetDisplay("Trailing Stop", "Trailing distance in price steps", "Risk")
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk")
.SetNotNegative();
_initialStopLossPoints = Param(nameof(InitialStopLossPoints), 0m)
.SetDisplay("Initial Stop Loss", "Initial stop loss in price steps", "Risk")
.SetNotNegative();
_fridayCutoffHour = Param(nameof(FridayCutoffHour), 16)
.SetDisplay("Friday Cutoff Hour", "Disable new entries after this hour on Friday", "Sessions")
.SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle type for signal calculations", "General");
Volume = 1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeHistory.Clear();
_highHistory.Clear();
_lowHistory.Clear();
_longTrailingStop = null;
_shortTrailingStop = null;
_entryPrice = 0m;
_previousSignal = 0;
_pointValue = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 1m;
if (_pointValue <= 0m)
{
_pointValue = 1m;
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
// protection handled manually via trailing/TP/SL
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
{
return;
}
UpdateHistory(candle);
// indicators are processed manually
if (_closeHistory.Count < CountBars + Ssp + 1)
{
return;
}
var jtpo = CalculateJtpo(JtpoLength);
var signal = CalculateSilverTrendSignal();
var longSignal = _previousSignal != signal && signal > 0 && jtpo > 0m;
var shortSignal = _previousSignal != signal && signal < 0 && jtpo < 0m;
var exitLong = _previousSignal < 0;
var exitShort = _previousSignal > 0;
ManageOpenPosition(candle, exitLong, exitShort);
if (Position <= 0 && longSignal && !IsFridayBlocked(candle))
{
EnterLong(candle);
}
else if (Position >= 0 && shortSignal && !IsFridayBlocked(candle))
{
EnterShort(candle);
}
_previousSignal = signal;
}
private void UpdateHistory(ICandleMessage candle)
{
_closeHistory.Add(candle.ClosePrice);
_highHistory.Add(candle.HighPrice);
_lowHistory.Add(candle.LowPrice);
if (_closeHistory.Count > HistoryCapacity)
{
_closeHistory.RemoveAt(0);
_highHistory.RemoveAt(0);
_lowHistory.RemoveAt(0);
}
}
private void ManageOpenPosition(ICandleMessage candle, bool exitLongSignal, bool exitShortSignal)
{
if (Position > 0)
{
UpdateLongTrailing(candle);
var initialStop = InitialStopLossPoints > 0m ? _entryPrice - GetDistance(InitialStopLossPoints) : (decimal?)null;
var trailingStop = _longTrailingStop;
var stop = CombineLongStops(initialStop, trailingStop);
var takeProfit = TakeProfitPoints > 0m ? _entryPrice + GetDistance(TakeProfitPoints) : (decimal?)null;
if (exitLongSignal ||
(takeProfit.HasValue && candle.HighPrice >= takeProfit.Value) ||
(stop.HasValue && candle.LowPrice <= stop.Value))
{
SellMarket();
ResetStops();
}
}
else if (Position < 0)
{
UpdateShortTrailing(candle);
var initialStop = InitialStopLossPoints > 0m ? _entryPrice + GetDistance(InitialStopLossPoints) : (decimal?)null;
var trailingStop = _shortTrailingStop;
var stop = CombineShortStops(initialStop, trailingStop);
var takeProfit = TakeProfitPoints > 0m ? _entryPrice - GetDistance(TakeProfitPoints) : (decimal?)null;
if (exitShortSignal ||
(takeProfit.HasValue && candle.LowPrice <= takeProfit.Value) ||
(stop.HasValue && candle.HighPrice >= stop.Value))
{
BuyMarket();
ResetStops();
}
}
else
{
ResetStops();
}
}
private void EnterLong(ICandleMessage candle)
{
var volume = Volume;
if (Position < 0)
{
volume += Math.Abs(Position);
}
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_longTrailingStop = null;
_shortTrailingStop = null;
}
private void EnterShort(ICandleMessage candle)
{
var volume = Volume;
if (Position > 0)
{
volume += Position;
}
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_longTrailingStop = null;
_shortTrailingStop = null;
}
private void UpdateLongTrailing(ICandleMessage candle)
{
if (TrailingStopPoints <= 0m)
{
return;
}
var distance = GetDistance(TrailingStopPoints);
var trigger = _entryPrice + distance;
if (candle.ClosePrice > trigger)
{
var newStop = candle.ClosePrice - distance;
if (!_longTrailingStop.HasValue || newStop > _longTrailingStop.Value)
{
_longTrailingStop = newStop;
}
}
}
private void UpdateShortTrailing(ICandleMessage candle)
{
if (TrailingStopPoints <= 0m)
{
return;
}
var distance = GetDistance(TrailingStopPoints);
var trigger = _entryPrice - distance;
if (candle.ClosePrice < trigger)
{
var newStop = candle.ClosePrice + distance;
if (!_shortTrailingStop.HasValue || newStop < _shortTrailingStop.Value)
{
_shortTrailingStop = newStop;
}
}
}
private decimal? CombineLongStops(decimal? initialStop, decimal? trailingStop)
{
if (initialStop == null && trailingStop == null)
{
return null;
}
if (initialStop == null)
{
return trailingStop;
}
if (trailingStop == null)
{
return initialStop;
}
return Math.Max(initialStop.Value, trailingStop.Value);
}
private decimal? CombineShortStops(decimal? initialStop, decimal? trailingStop)
{
if (initialStop == null && trailingStop == null)
{
return null;
}
if (initialStop == null)
{
return trailingStop;
}
if (trailingStop == null)
{
return initialStop;
}
return Math.Min(initialStop.Value, trailingStop.Value);
}
private void ResetStops()
{
if (Position == 0)
{
_entryPrice = 0m;
}
_longTrailingStop = null;
_shortTrailingStop = null;
}
private bool IsFridayBlocked(ICandleMessage candle)
{
if (FridayCutoffHour <= 0)
{
return false;
}
var time = candle.OpenTime;
return time.DayOfWeek == DayOfWeek.Friday && time.Hour > FridayCutoffHour;
}
private int CalculateSilverTrendSignal()
{
var k = 33 - Risk;
var uptrend = false;
var val = 0;
for (var i = CountBars - Ssp; i >= 0; i--)
{
var ssMax = GetHigh(i);
var ssMin = GetLow(i);
for (var i2 = i; i2 <= i + Ssp - 1; i2++)
{
var priceHigh = GetHigh(i2);
if (ssMax < priceHigh)
{
ssMax = priceHigh;
}
var priceLow = GetLow(i2);
if (ssMin >= priceLow)
{
ssMin = priceLow;
}
}
var smin = ssMin + (ssMax - ssMin) * k / 100m;
var smax = ssMax - (ssMax - ssMin) * k / 100m;
if (GetClose(i) < smin)
{
uptrend = false;
}
if (GetClose(i) > smax)
{
uptrend = true;
}
val = uptrend ? 1 : -1;
}
return val;
}
private decimal CalculateJtpo(int len)
{
if (_closeHistory.Count < 200)
{
return 0m;
}
decimal f8 = 0m;
decimal f10 = 0m;
decimal f18 = 0m;
decimal f20 = 0m;
decimal f30 = 0m;
decimal f40 = 0m;
decimal k = 0m;
decimal var14 = 0m;
decimal var18 = 0m;
decimal var1C = 0m;
decimal var20 = 0m;
decimal var24 = 0m;
decimal value = 0m;
var f38 = 0;
var f48 = 0;
var arr0 = new decimal[400];
var arr1 = new decimal[400];
var arr2 = new decimal[400];
var arr3 = new decimal[400];
for (var i = 200 - len - 100; i >= 0; i--)
{
var14 = 0m;
var1C = 0m;
if (f38 == 0)
{
f38 = 1;
f40 = 0m;
f30 = len - 1 >= 2 ? len - 1 : 2;
f48 = (int)f30 + 1;
f10 = GetClose(i);
arr0[f38] = f10;
k = f48;
f18 = 12m / (k * (k - 1) * (k + 1));
f20 = (f48 + 1) * 0.5m;
}
else
{
if (f38 <= f48)
{
f38 += 1;
}
else
{
f38 = f48 + 1;
}
f8 = f10;
f10 = GetClose(i);
if (f38 > f48)
{
for (var var6 = 2; var6 <= f48; var6++)
{
arr0[var6 - 1] = arr0[var6];
}
arr0[f48] = f10;
}
else
{
arr0[f38] = f10;
}
if (f30 >= f38 && f8 != f10)
{
f40 = 1m;
}
if (f30 == f38 && f40 == 0m)
{
f38 = 0;
}
}
if (f38 >= f48)
{
for (var varA = 1; varA <= f48; varA++)
{
arr2[varA] = varA;
arr3[varA] = varA;
arr1[varA] = arr0[varA];
}
for (var varA = 1; varA <= f48 - 1; varA++)
{
var24 = arr1[varA];
var var12 = varA;
for (var var6 = varA + 1; var6 <= f48; var6++)
{
if (arr1[var6] < var24)
{
var24 = arr1[var6];
var12 = var6;
}
}
var20 = arr1[varA];
arr1[varA] = arr1[var12];
arr1[var12] = var20;
var20 = arr2[varA];
arr2[varA] = arr2[var12];
arr2[var12] = var20;
}
var varIndex = 1;
while (f48 > varIndex)
{
var var6 = varIndex + 1;
var14 = 1m;
var1C = arr3[varIndex];
while (var14 != 0m && var6 < arr3.Length)
{
if (arr1[varIndex] != arr1[var6])
{
if ((var6 - varIndex) > 1)
{
var1C /= (var6 - varIndex);
for (var varE = varIndex; varE <= var6 - 1; varE++)
{
arr3[varE] = var1C;
}
}
var14 = 0m;
}
else
{
var1C += arr3[var6];
var6 += 1;
if (var6 > f48 + 1)
{
break;
}
}
}
varIndex = var6;
}
var1C = 0m;
for (var varA = 1; varA <= f48; varA++)
{
var1C += (arr3[varA] - f20) * (arr2[varA] - f20);
}
var18 = f18 * var1C;
}
else
{
var18 = 0m;
}
value = var18;
if (value == 0m)
{
value = 0.00001m;
}
}
return value;
}
private decimal GetClose(int shift)
{
var index = _closeHistory.Count - 1 - shift;
if (index < 0)
{
index = 0;
}
return _closeHistory[index];
}
private decimal GetHigh(int shift)
{
var index = _highHistory.Count - 1 - shift;
if (index < 0)
{
index = 0;
}
return _highHistory[index];
}
private decimal GetLow(int shift)
{
var index = _lowHistory.Count - 1 - shift;
if (index < 0)
{
index = 0;
}
return _lowHistory[index];
}
private decimal GetDistance(decimal points)
{
return points * _pointValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class silver_trend_v3_strategy(Strategy):
def __init__(self):
super(silver_trend_v3_strategy, self).__init__()
self._count_bars = self.Param("CountBars", 150)
self._ssp = self.Param("Ssp", 9)
self._jtpo_length = self.Param("JtpoLength", 14)
self._history_capacity = self.Param("HistoryCapacity", 220)
self._risk = self.Param("Risk", 3)
self._trailing_points = self.Param("TrailingStopPoints", 50.0)
self._tp_points = self.Param("TakeProfitPoints", 50.0)
self._sl_points = self.Param("InitialStopLossPoints", 0.0)
self._friday_cutoff = self.Param("FridayCutoffHour", 16)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self.Volume = 1
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(silver_trend_v3_strategy, self).OnReseted()
self._close_hist = []
self._high_hist = []
self._low_hist = []
self._long_trailing = None
self._short_trailing = None
self._entry_price = 0.0
self._prev_signal = 0
self._point_value = 0.0
def OnStarted2(self, time):
super(silver_trend_v3_strategy, self).OnStarted2(time)
self._close_hist = []
self._high_hist = []
self._low_hist = []
self._long_trailing = None
self._short_trailing = None
self._entry_price = 0.0
self._prev_signal = 0
pv = 1.0
if self.Security is not None and self.Security.PriceStep is not None and float(self.Security.PriceStep) > 0:
pv = float(self.Security.PriceStep)
self._point_value = pv
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_history(candle)
cb = int(self._count_bars.Value)
ssp = int(self._ssp.Value)
if len(self._close_hist) < cb + ssp + 1:
return
jtpo = self._calc_jtpo(int(self._jtpo_length.Value))
signal = self._calc_signal()
long_signal = self._prev_signal != signal and signal > 0 and jtpo > 0
short_signal = self._prev_signal != signal and signal < 0 and jtpo < 0
exit_long = self._prev_signal < 0
exit_short = self._prev_signal > 0
self._manage_position(candle, exit_long, exit_short)
if self.Position <= 0 and long_signal and not self._is_friday_blocked(candle):
self._enter_long(candle)
elif self.Position >= 0 and short_signal and not self._is_friday_blocked(candle):
self._enter_short(candle)
self._prev_signal = signal
def _update_history(self, candle):
self._close_hist.append(float(candle.ClosePrice))
self._high_hist.append(float(candle.HighPrice))
self._low_hist.append(float(candle.LowPrice))
cap = int(self._history_capacity.Value)
if len(self._close_hist) > cap:
self._close_hist.pop(0)
self._high_hist.pop(0)
self._low_hist.pop(0)
def _manage_position(self, candle, exit_long_signal, exit_short_signal):
if self.Position > 0:
self._update_long_trailing(candle)
initial_stop = None
if float(self._sl_points.Value) > 0:
initial_stop = self._entry_price - self._get_distance(float(self._sl_points.Value))
trailing_stop = self._long_trailing
stop = self._combine_long_stops(initial_stop, trailing_stop)
take_profit = None
if float(self._tp_points.Value) > 0:
take_profit = self._entry_price + self._get_distance(float(self._tp_points.Value))
if exit_long_signal or \
(take_profit is not None and float(candle.HighPrice) >= take_profit) or \
(stop is not None and float(candle.LowPrice) <= stop):
self.SellMarket()
self._reset_stops()
elif self.Position < 0:
self._update_short_trailing(candle)
initial_stop = None
if float(self._sl_points.Value) > 0:
initial_stop = self._entry_price + self._get_distance(float(self._sl_points.Value))
trailing_stop = self._short_trailing
stop = self._combine_short_stops(initial_stop, trailing_stop)
take_profit = None
if float(self._tp_points.Value) > 0:
take_profit = self._entry_price - self._get_distance(float(self._tp_points.Value))
if exit_short_signal or \
(take_profit is not None and float(candle.LowPrice) <= take_profit) or \
(stop is not None and float(candle.HighPrice) >= stop):
self.BuyMarket()
self._reset_stops()
else:
self._reset_stops()
def _enter_long(self, candle):
volume = float(self.Volume)
if self.Position < 0:
volume += abs(float(self.Position))
self.BuyMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._long_trailing = None
self._short_trailing = None
def _enter_short(self, candle):
volume = float(self.Volume)
if self.Position > 0:
volume += float(self.Position)
self.SellMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._long_trailing = None
self._short_trailing = None
def _update_long_trailing(self, candle):
tp = float(self._trailing_points.Value)
if tp <= 0:
return
dist = self._get_distance(tp)
trigger = self._entry_price + dist
close = float(candle.ClosePrice)
if close > trigger:
new_stop = close - dist
if self._long_trailing is None or new_stop > self._long_trailing:
self._long_trailing = new_stop
def _update_short_trailing(self, candle):
tp = float(self._trailing_points.Value)
if tp <= 0:
return
dist = self._get_distance(tp)
trigger = self._entry_price - dist
close = float(candle.ClosePrice)
if close < trigger:
new_stop = close + dist
if self._short_trailing is None or new_stop < self._short_trailing:
self._short_trailing = new_stop
def _combine_long_stops(self, initial, trailing):
if initial is None and trailing is None:
return None
if initial is None:
return trailing
if trailing is None:
return initial
return max(initial, trailing)
def _combine_short_stops(self, initial, trailing):
if initial is None and trailing is None:
return None
if initial is None:
return trailing
if trailing is None:
return initial
return min(initial, trailing)
def _reset_stops(self):
if self.Position == 0:
self._entry_price = 0.0
self._long_trailing = None
self._short_trailing = None
def _is_friday_blocked(self, candle):
cutoff = int(self._friday_cutoff.Value)
if cutoff <= 0:
return False
t = candle.OpenTime
return t.DayOfWeek == 5 and t.Hour > cutoff
def _calc_signal(self):
k = 33 - int(self._risk.Value)
ssp = int(self._ssp.Value)
cb = int(self._count_bars.Value)
uptrend = False
val = 0
for i in range(cb - ssp, -1, -1):
ss_max = self._get_high(i)
ss_min = self._get_low(i)
for i2 in range(i, i + ssp):
h = self._get_high(i2)
if ss_max < h:
ss_max = h
lo = self._get_low(i2)
if ss_min >= lo:
ss_min = lo
smin_val = ss_min + (ss_max - ss_min) * k / 100.0
smax_val = ss_max - (ss_max - ss_min) * k / 100.0
c = self._get_close(i)
if c < smin_val:
uptrend = False
if c > smax_val:
uptrend = True
val = 1 if uptrend else -1
return val
def _calc_jtpo(self, length):
if len(self._close_hist) < 200:
return 0.0
f8 = 0.0
f10 = 0.0
f18 = 0.0
f20 = 0.0
f30 = 0.0
f40 = 0.0
k = 0.0
var14 = 0.0
var18 = 0.0
var1C = 0.0
var20 = 0.0
var24 = 0.0
value = 0.0
f38 = 0
f48 = 0
arr0 = [0.0] * 400
arr1 = [0.0] * 400
arr2 = [0.0] * 400
arr3 = [0.0] * 400
for i in range(200 - length - 100, -1, -1):
var14 = 0.0
var1C = 0.0
if f38 == 0:
f38 = 1
f40 = 0.0
f30 = float(length - 1) if length - 1 >= 2 else 2.0
f48 = int(f30) + 1
f10 = self._get_close(i)
arr0[f38] = f10
k = float(f48)
f18 = 12.0 / (k * (k - 1.0) * (k + 1.0))
f20 = (f48 + 1) * 0.5
else:
if f38 <= f48:
f38 += 1
else:
f38 = f48 + 1
f8 = f10
f10 = self._get_close(i)
if f38 > f48:
for var6 in range(2, f48 + 1):
arr0[var6 - 1] = arr0[var6]
arr0[f48] = f10
else:
arr0[f38] = f10
if f30 >= f38 and f8 != f10:
f40 = 1.0
if f30 == f38 and f40 == 0.0:
f38 = 0
if f38 >= f48:
for varA in range(1, f48 + 1):
arr2[varA] = float(varA)
arr3[varA] = float(varA)
arr1[varA] = arr0[varA]
for varA in range(1, f48):
var24 = arr1[varA]
var12 = varA
for var6 in range(varA + 1, f48 + 1):
if arr1[var6] < var24:
var24 = arr1[var6]
var12 = var6
var20 = arr1[varA]
arr1[varA] = arr1[var12]
arr1[var12] = var20
var20 = arr2[varA]
arr2[varA] = arr2[var12]
arr2[var12] = var20
varIndex = 1
while f48 > varIndex:
var6 = varIndex + 1
var14 = 1.0
var1C = arr3[varIndex]
while var14 != 0.0 and var6 < len(arr3):
if arr1[varIndex] != arr1[var6]:
if (var6 - varIndex) > 1:
var1C /= float(var6 - varIndex)
for varE in range(varIndex, var6):
arr3[varE] = var1C
var14 = 0.0
else:
var1C += arr3[var6]
var6 += 1
if var6 > f48 + 1:
break
varIndex = var6
var1C = 0.0
for varA in range(1, f48 + 1):
var1C += (arr3[varA] - f20) * (arr2[varA] - f20)
var18 = f18 * var1C
else:
var18 = 0.0
value = var18
if value == 0.0:
value = 0.00001
return value
def _get_close(self, shift):
idx = len(self._close_hist) - 1 - shift
if idx < 0:
idx = 0
return self._close_hist[idx]
def _get_high(self, shift):
idx = len(self._high_hist) - 1 - shift
if idx < 0:
idx = 0
return self._high_hist[idx]
def _get_low(self, shift):
idx = len(self._low_hist) - 1 - shift
if idx < 0:
idx = 0
return self._low_hist[idx]
def _get_distance(self, points):
return points * self._point_value
def CreateClone(self):
return silver_trend_v3_strategy()