SilverTrend V3 策略(C#)
概述
SilverTrend V3 策略源自 MetaTrader 5 的 "SilverTrend v3" 专家顾问,本移植版本使用 StockSharp 高级 API 重写原始逻辑。策略通过 SilverTrend 通道判断趋势方向,并结合 J_TPO 市场轮廓振荡指标进行过滤,随后利用止损、止盈、移动止损以及周五过滤器对持仓进行完整的风险管理。
信号机制
SilverTrend 趋势判断
- 使用 350 根 K 线窗口并配合 9 根平滑参数来计算动态支撑(
smin)与阻力(smax)。 - 收盘价跌破
smin时视为看空环境;收盘价突破smax时视为看多环境。 - 计算过程从最早的历史数据递推至最新一根,以保留原始 MQL 版本的递归特性。
- 使用 350 根 K 线窗口并配合 9 根平滑参数来计算动态支撑(
J_TPO 确认
- 实现原始代码中的 14 周期 J_TPO 指标,用于衡量价格在短期区间内的聚集情况。
- 当指标为正时才允许做多,当指标为负时才允许做空,从而过滤掉噪音信号。
趋势切换检测
- 仅在 SilverTrend 趋势方向发生变化时开仓,避免在无效波动中频繁交易。
交易管理
- 市价开仓:使用策略属性
Volume指定的手数。如果存在反向持仓,会在同一笔市价单中平仓并反手。 - 初始止损:可选参数,以价格步长为单位,相对于进场价计算;自动根据标的的
PriceStep转换为实际价格距离。 - 止盈目标:同样以价格步长定义,使用当前 K 线的最高/最低价检测是否达到目标,从而模拟原策略的订单修改行为。
- 移动止损:当价格向有利方向移动超过设定距离后启动。多单时止损上移,空单时止损下移,逻辑与原始 EA 保持一致。
- 反向信号离场:当上一根信号显示相反方向时,会在下一根完成的 K 线上平掉现有持仓。
- 周五交易限制:周五超过指定小时后不再开新仓,以避免周末跳空风险。
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
TrailingStopPoints |
50 | 移动止损距离(价格步)。设为 0 则关闭移动止损。 |
TakeProfitPoints |
50 | 止盈目标(价格步)。设为 0 则取消止盈。 |
InitialStopLossPoints |
0 | 初始止损(价格步)。设为 0 则不使用初始止损。 |
FridayCutoffHour |
16 | 周五超过该小时后不再开新仓。设为 0 可整日交易。 |
CandleType |
1 小时 K 线 | 用于计算信号的数据类型,可根据需要调整。 |
Volume |
1 手 | 每次交易的数量,使用 StockSharp 的 Volume 属性配置。 |
所有距离类参数都会在运行时乘以 PriceStep,因此能自动适配不同标的的最小跳动单位(包括 3/5 位报价的外汇品种)。
数据与环境要求
- 需要至少 360 根完整 K 线后才会生成信号,以保证 SilverTrend 与 J_TPO 缓冲区完成初始化。
- 策略仅针对单一标的,通过
SubscribeCandles订阅所需的 K 线数据,GetWorkingSecurities会返回相应的证券与周期。 - 在
OnStarted中调用StartProtection(),以启用 StockSharp 提供的基础持仓保护服务。
使用建议
- 建议应用于趋势性较强、流动性较好的品种(如主要外汇对或热门期货品种),并根据波动率调整时间框架。
- 由于 SilverTrend 计算具有递归特性,若在历史数据不足的情况下启动策略,需要等待足够的 K 线数据后才会开始交易。
- 当前实现基于 K 线最高/最低价模拟止损和止盈触发。如果在真实交易环境需要委托级别的风险控制,可结合实际止损/止盈订单一起使用。
_previousSignal、_entryPrice与移动止损状态只会在每根完成的 K 线上更新一次,与原 EA 的“一根 K 线一轮决策”行为保持一致。
移植细节
- 完整复刻
SilverTrend v3.mq5中的数学算法,包括多维数组实现的 J_TPO 计算。 - 遵循仓库规范:所有参数通过
StrategyParam<T>暴露、注释为英文、并使用制表符缩进。 - 根据任务要求,本次仅提供 C# 版本,未创建 Python 版本或对应目录。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// 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()