OpenTiks 策略
概述
OpenTiks 策略将 MetaTrader 顾问 OpenTiks.mq4 迁移到 StockSharp 平台。原始机器人通过寻找高点和开盘价都严格
单调的阶梯形 K 线序列来捕捉早期突破。出现信号后,它会立即以市价建仓,可选地设置止损,并在行情向有利方向运行时
不断上移止损并分批减仓。StockSharp 版本保留了这些思路,利用高层 API、蜡烛图订阅以及内置下单工具,使策略可以在
Designer、Runner 或任何自建 S# 应用中运行。
模式识别
当出现以下两种模式之一时将发出交易信号(共需 四根连续 K 线):
- 多头突破:当前 K 线及前三根 K 线的
High值严格递增,同时Open值也严格递增。 - 空头突破:同一窗口内的四根 K 线
High值严格递减,并且Open值严格递减。
策略使用所选 CandleType 的已完成蜡烛进行判断。一旦条件成立,便按设定的手数发送市价单,并根据交易品种的
VolumeStep、MinVolume、MaxVolume 自动调整实际下单量。MaxOrders 用于限制同一时间最多允许的持仓次数,
设为 0 表示不限制,正整数则在净头寸绝对值除以标准下单量达到阈值时阻止新的加仓。
风险控制与出场
- 止损:当
StopLossPoints大于 0 时,策略会监控最新收盘 K 线。多头在最低价跌破entryPrice - StopLossPoints × PriceStep时平仓;空头在最高价触及entryPrice + StopLossPoints × PriceStep时离场。 - 移动止损:当价格向有利方向运行至少
TrailingStopPoints × PriceStep后,策略会启动追踪止损,并保持 相同的距离(多头在价格下方、空头在价格上方)。每次止损水平被提升时,都可以进一步锁定利润。 - 逐步减仓:启用
UsePartialClose后,只要移动止损再次上移,策略就会把当前仓位减半。下单量会按照VolumeStep取整,如果得到的半仓小于MinVolume,则改为一次性平仓,与原版 EA 的处理保持一致。
所有止损与追踪逻辑均基于收盘数据执行,因此实际离场发生在下一根 K 线收盘时,而不是每笔成交都响应。这种实现方式 既符合 StockSharp 的高层 API 设计,又贴近原始策略“以新 K 线为驱动”的思路。
参数
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OrderVolume |
decimal |
0.1 |
每次建仓的基础手数,会根据品种的成交量步长及限制自动调整。 |
StopLossPoints |
decimal |
0 |
止损距离(以价格点/最小价位计)。为 0 时不开启止损。 |
TrailingStopPoints |
decimal |
30 |
当仓位盈利后所维持的追踪止损距离,同样以价格点表示。 |
MaxOrders |
int |
1 |
同时存在的最大建仓次数。0 表示不限次数。 |
UsePartialClose |
bool |
true |
启用逐步减仓,在追踪止损更新时自动对冲仓位。 |
CandleType |
DataType |
1 分钟时间框架 | 用于信号判断和止损监控的主要蜡烛类型。 |
实现细节
- StockSharp 使用 净头寸模型,同一品种的所有交易会合并为一个多头或空头仓位。因此
MaxOrders实际上限制的是 合并后的净头寸,而不是 MetaTrader 中的单独订单数量。 - 移动止损依赖蜡烛收盘进行计算,如需更细粒度的保护,可以选择更短的
CandleType,或扩展策略订阅实时成交数据。 - 逐步减仓会参考交易品种的
VolumeStep、MinVolume、MaxVolume,以尽量避免因数量不合规而被交易所拒单。 - 代码中的英文注释标注了关键决策点,方便在二次开发或研究不同突破/资金管理方法时参考。
使用建议
- 选择与原始 EA 相符的蜡烛时间框架(例如 M1 或 M5),以复现同样的交易节奏。
- 根据交易品种检查价格步长和最小手数,默认的
0.1更适合外汇合约,如需交易期货、股票或加密货币可自行调整。 - 调整
TrailingStopPoints和UsePartialClose,寻找在快速锁盈与让利润奔跑之间的最佳平衡。 - 搭配 StockSharp 图表观察阶梯形走势与分批止盈过程,便于理解和优化策略行为。
namespace StockSharp.Samples.Strategies;
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;
using StockSharp.Algo;
/// <summary>
/// Reimplementation of the MetaTrader expert advisor "OpenTiks" for StockSharp.
/// Detects four consecutive candles with strictly monotonic opens and highs to trigger entries,
/// then manages the position with optional stop-loss, trailing stop and progressive partial exits.
/// </summary>
public class OpenTiksStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _maxOrders;
private readonly StrategyParam<bool> _usePartialClose;
private readonly StrategyParam<DataType> _candleType;
private decimal _priceStep;
private decimal _volumeStep;
private decimal _minVolumeLimit;
private decimal _maxVolumeLimit;
private decimal? _high1;
private decimal? _high2;
private decimal? _high3;
private decimal? _open1;
private decimal? _open2;
private decimal? _open3;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private SimpleMovingAverage _dummySma;
private decimal _previousPosition;
private decimal? _lastTradePrice;
/// <summary>
/// Initializes a new instance of the <see cref="OpenTiksStrategy"/> class.
/// </summary>
public OpenTiksStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each market entry in lots.", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points.", "Risk");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
.SetNotNegative()
.SetDisplay("Trailing Stop (points)", "Trailing distance expressed in price points.", "Risk");
_maxOrders = Param(nameof(MaxOrders), 1)
.SetNotNegative()
.SetDisplay("Max Orders", "Maximum number of simultaneously open entries. Zero disables the limit.", "Trading");
_usePartialClose = Param(nameof(UsePartialClose), true)
.SetDisplay("Use Partial Close", "Close half of the position whenever the trailing stop advances.", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used for pattern detection.", "General");
}
/// <summary>
/// Order volume used for every market entry.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set
{
_orderVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Maximum number of simultaneously open entries.
/// </summary>
public int MaxOrders
{
get => _maxOrders.Value;
set => _maxOrders.Value = value;
}
/// <summary>
/// Enables progressive partial exits when true.
/// </summary>
public bool UsePartialClose
{
get => _usePartialClose.Value;
set => _usePartialClose.Value = value;
}
/// <summary>
/// Candle type requested from the market data feed.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_priceStep = 0;
_volumeStep = 0;
_minVolumeLimit = 0;
_maxVolumeLimit = 0;
_high1 = null;
_high2 = null;
_high3 = null;
_open1 = null;
_open2 = null;
_open3 = null;
_longEntryPrice = null;
_shortEntryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_dummySma = null;
_previousPosition = 0m;
_lastTradePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var security = Security;
_priceStep = security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_volumeStep = security?.VolumeStep ?? 0m;
_minVolumeLimit = security?.MinVolume ?? 0m;
_maxVolumeLimit = security?.MaxVolume ?? 0m;
Volume = NormalizeEntryVolume(OrderVolume);
_dummySma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_dummySma, ProcessCandle)
.Start();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
_lastTradePrice = trade.Trade?.Price ?? trade.Order.Price;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
var delta = Position - _previousPosition;
if (Position > 0m)
{
if (_previousPosition <= 0m)
{
_longEntryPrice = _lastTradePrice;
_longTrailingStop = null;
_shortEntryPrice = null;
_shortTrailingStop = null;
}
else if (delta > 0m && _lastTradePrice is decimal priceLong)
{
var previousVolume = Math.Max(0m, _previousPosition);
var currentVolume = Math.Max(0m, Position);
if (currentVolume > 0m)
{
var currentEntry = _longEntryPrice ?? priceLong;
_longEntryPrice = (currentEntry * previousVolume + priceLong * delta) / currentVolume;
}
}
}
else if (Position < 0m)
{
if (_previousPosition >= 0m)
{
_shortEntryPrice = _lastTradePrice;
_shortTrailingStop = null;
_longEntryPrice = null;
_longTrailingStop = null;
}
else if (delta < 0m && _lastTradePrice is decimal priceShort)
{
var previousVolume = Math.Max(0m, Math.Abs(_previousPosition));
var currentVolume = Math.Max(0m, Math.Abs(Position));
if (currentVolume > 0m)
{
var currentEntry = _shortEntryPrice ?? priceShort;
_shortEntryPrice = (currentEntry * previousVolume + priceShort * Math.Abs(delta)) / currentVolume;
}
}
}
else
{
_longEntryPrice = null;
_shortEntryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
}
_previousPosition = Position;
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateTrailing(candle);
var buySignal = false;
var sellSignal = false;
if (_high1 is decimal h1 && _high2 is decimal h2 && _high3 is decimal h3 &&
_open1 is decimal o1 && _open2 is decimal o2 && _open3 is decimal o3)
{
var high = candle.HighPrice;
var open = candle.OpenPrice;
buySignal = high > h1 && h1 > h2 && h2 > h3 &&
open > o1 && o1 > o2 && o2 > o3;
sellSignal = high < h1 && h1 < h2 && h2 < h3 &&
open < o1 && o1 < o2 && o2 < o3;
}
_high3 = _high2;
_high2 = _high1;
_high1 = candle.HighPrice;
_open3 = _open2;
_open2 = _open1;
_open1 = candle.OpenPrice;
if (buySignal)
TryEnterLong();
if (sellSignal)
TryEnterShort();
}
private void TryEnterLong()
{
if (MaxOrders > 0 && EstimateOrdersCount(Position) >= MaxOrders)
return;
var volume = NormalizeEntryVolume(OrderVolume);
if (volume <= 0m)
return;
BuyMarket(volume);
}
private void TryEnterShort()
{
if (MaxOrders > 0 && EstimateOrdersCount(Position) >= MaxOrders)
return;
var volume = NormalizeEntryVolume(OrderVolume);
if (volume <= 0m)
return;
SellMarket(volume);
}
private int EstimateOrdersCount(decimal positionVolume)
{
var baseVolume = NormalizeEntryVolume(OrderVolume);
if (baseVolume <= 0m)
return positionVolume != 0m ? 1 : 0;
var ratio = Math.Abs(positionVolume) / baseVolume;
if (ratio <= 0m)
return 0;
return (int)Math.Ceiling(ratio);
}
private void UpdateTrailing(ICandleMessage candle)
{
var close = candle.ClosePrice;
var low = candle.LowPrice;
var high = candle.HighPrice;
var stopDistance = StopLossPoints * _priceStep;
var trailingDistance = TrailingStopPoints * _priceStep;
if (Position > 0m && _longEntryPrice is decimal entryLong)
{
if (stopDistance > 0m && low <= entryLong - stopDistance)
{
SellMarket(Position);
return;
}
if (trailingDistance > 0m && close - entryLong >= trailingDistance)
{
var desiredStop = close - trailingDistance;
if (_longTrailingStop is not decimal currentStop || desiredStop > currentStop)
{
_longTrailingStop = desiredStop;
TryReduceLongPosition();
}
if (_longTrailingStop is decimal trailingStop && low <= trailingStop)
SellMarket(Position);
}
}
else if (Position < 0m && _shortEntryPrice is decimal entryShort)
{
var positionVolume = Math.Abs(Position);
if (stopDistance > 0m && high >= entryShort + stopDistance)
{
BuyMarket(positionVolume);
return;
}
if (trailingDistance > 0m && entryShort - close >= trailingDistance)
{
var desiredStop = close + trailingDistance;
if (_shortTrailingStop is not decimal currentStop || desiredStop < currentStop)
{
_shortTrailingStop = desiredStop;
TryReduceShortPosition();
}
if (_shortTrailingStop is decimal trailingStop && high >= trailingStop)
BuyMarket(positionVolume);
}
}
}
private void TryReduceLongPosition()
{
if (!UsePartialClose)
return;
if (Position <= 0m)
return;
var positionVolume = Position;
var half = positionVolume / 2m;
var normalizedHalf = NormalizeExitVolume(half, positionVolume);
if (_minVolumeLimit > 0m && normalizedHalf < _minVolumeLimit)
{
SellMarket(positionVolume);
return;
}
if (normalizedHalf > 0m)
SellMarket(normalizedHalf);
}
private void TryReduceShortPosition()
{
if (!UsePartialClose)
return;
if (Position >= 0m)
return;
var positionVolume = Math.Abs(Position);
var half = positionVolume / 2m;
var normalizedHalf = NormalizeExitVolume(half, positionVolume);
if (_minVolumeLimit > 0m && normalizedHalf < _minVolumeLimit)
{
BuyMarket(positionVolume);
return;
}
if (normalizedHalf > 0m)
BuyMarket(normalizedHalf);
}
private decimal NormalizeEntryVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
if (_volumeStep > 0m)
{
var steps = Math.Round(volume / _volumeStep, MidpointRounding.AwayFromZero);
if (steps <= 0m)
steps = 1m;
volume = steps * _volumeStep;
}
if (_minVolumeLimit > 0m && volume < _minVolumeLimit)
volume = _minVolumeLimit;
if (_maxVolumeLimit > 0m && volume > _maxVolumeLimit)
volume = _maxVolumeLimit;
return volume;
}
private decimal NormalizeExitVolume(decimal desired, decimal currentPosition)
{
if (desired <= 0m || currentPosition <= 0m)
return 0m;
var volume = desired;
if (_volumeStep > 0m)
{
var steps = Math.Round(volume / _volumeStep, MidpointRounding.AwayFromZero);
if (steps <= 0m)
steps = 1m;
volume = steps * _volumeStep;
}
if (volume > currentPosition)
volume = currentPosition;
return volume;
}
}
import clr
import math
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, Sides
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class open_tiks_strategy(Strategy):
def __init__(self):
super(open_tiks_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetDisplay("Order Volume", "Volume of each market entry in lots", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 0.0) \
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 30.0) \
.SetDisplay("Trailing Stop (points)", "Trailing distance expressed in price points", "Risk")
self._max_orders = self.Param("MaxOrders", 1) \
.SetDisplay("Max Orders", "Maximum number of simultaneously open entries", "Trading")
self._use_partial_close = self.Param("UsePartialClose", True) \
.SetDisplay("Use Partial Close", "Close half of the position whenever the trailing stop advances", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe used for pattern detection", "General")
self._price_step = 1.0
self._volume_step = 0.0
self._min_volume_limit = 0.0
self._max_volume_limit = 0.0
self._high1 = None
self._high2 = None
self._high3 = None
self._open1 = None
self._open2 = None
self._open3 = None
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def MaxOrders(self):
return self._max_orders.Value
@property
def UsePartialClose(self):
return self._use_partial_close.Value
@property
def CandleType(self):
return self._candle_type.Value
def _normalize_entry_volume(self, volume):
if volume <= 0:
return 0.0
if self._volume_step > 0:
steps = round(volume / self._volume_step)
if steps <= 0:
steps = 1
volume = steps * self._volume_step
if self._min_volume_limit > 0 and volume < self._min_volume_limit:
volume = self._min_volume_limit
if self._max_volume_limit > 0 and volume > self._max_volume_limit:
volume = self._max_volume_limit
return volume
def _normalize_exit_volume(self, desired, current_position):
if desired <= 0 or current_position <= 0:
return 0.0
volume = desired
if self._volume_step > 0:
steps = round(volume / self._volume_step)
if steps <= 0:
steps = 1
volume = steps * self._volume_step
if volume > current_position:
volume = current_position
return volume
def OnStarted2(self, time):
super(open_tiks_strategy, self).OnStarted2(time)
self._price_step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
self._price_step = ps
self._volume_step = 0.0
if self.Security is not None and self.Security.VolumeStep is not None:
vs = float(self.Security.VolumeStep)
if vs > 0:
self._volume_step = vs
self._min_volume_limit = 0.0
if self.Security is not None and self.Security.MinVolume is not None:
mv = float(self.Security.MinVolume)
if mv > 0:
self._min_volume_limit = mv
self._max_volume_limit = 0.0
if self.Security is not None and self.Security.MaxVolume is not None:
mv = float(self.Security.MaxVolume)
if mv > 0:
self._max_volume_limit = mv
self.Volume = self._normalize_entry_volume(float(self.OrderVolume))
self._dummy_sma = SimpleMovingAverage()
self._dummy_sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._dummy_sma, self.ProcessCandle).Start()
def OnOwnTradeReceived(self, trade):
super(open_tiks_strategy, self).OnOwnTradeReceived(trade)
if trade.Trade is not None and trade.Trade.Price is not None:
self._last_trade_price = float(trade.Trade.Price)
elif trade.Order is not None and trade.Order.Price is not None:
self._last_trade_price = float(trade.Order.Price)
def OnPositionReceived(self, position):
super(open_tiks_strategy, self).OnPositionReceived(position)
delta = float(self.Position) - self._previous_position
if self.Position > 0:
if self._previous_position <= 0:
self._long_entry_price = self._last_trade_price
self._long_trailing_stop = None
self._short_entry_price = None
self._short_trailing_stop = None
elif delta > 0 and self._last_trade_price is not None:
prev_vol = max(0.0, float(self._previous_position))
cur_vol = max(0.0, float(self.Position))
if cur_vol > 0:
current_entry = self._long_entry_price if self._long_entry_price is not None else self._last_trade_price
self._long_entry_price = (current_entry * prev_vol + self._last_trade_price * delta) / cur_vol
elif self.Position < 0:
if self._previous_position >= 0:
self._short_entry_price = self._last_trade_price
self._short_trailing_stop = None
self._long_entry_price = None
self._long_trailing_stop = None
elif delta < 0 and self._last_trade_price is not None:
prev_vol = max(0.0, abs(float(self._previous_position)))
cur_vol = max(0.0, float(float(Math.Abs(self.Position))))
if cur_vol > 0:
current_entry = self._short_entry_price if self._short_entry_price is not None else self._last_trade_price
self._short_entry_price = (current_entry * prev_vol + self._last_trade_price * abs(delta)) / cur_vol
else:
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = float(self.Position)
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
self._update_trailing(candle)
buy_signal = False
sell_signal = False
if (self._high1 is not None and self._high2 is not None and self._high3 is not None and
self._open1 is not None and self._open2 is not None and self._open3 is not None):
high = float(candle.HighPrice)
open_p = float(candle.OpenPrice)
buy_signal = (high > self._high1 and self._high1 > self._high2 and self._high2 > self._high3 and
open_p > self._open1 and self._open1 > self._open2 and self._open2 > self._open3)
sell_signal = (high < self._high1 and self._high1 < self._high2 and self._high2 < self._high3 and
open_p < self._open1 and self._open1 < self._open2 and self._open2 < self._open3)
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._open3 = self._open2
self._open2 = self._open1
self._open1 = float(candle.OpenPrice)
if buy_signal:
self._try_enter_long()
if sell_signal:
self._try_enter_short()
def _try_enter_long(self):
max_ord = self.MaxOrders
if max_ord > 0 and self._estimate_orders_count(self.Position) >= max_ord:
return
volume = self._normalize_entry_volume(float(self.OrderVolume))
if volume <= 0:
return
self.BuyMarket(volume)
def _try_enter_short(self):
max_ord = self.MaxOrders
if max_ord > 0 and self._estimate_orders_count(self.Position) >= max_ord:
return
volume = self._normalize_entry_volume(float(self.OrderVolume))
if volume <= 0:
return
self.SellMarket(volume)
def _estimate_orders_count(self, position_volume):
base_volume = self._normalize_entry_volume(float(self.OrderVolume))
if base_volume <= 0:
return 1 if position_volume != 0 else 0
ratio = float(Math.Abs(position_volume)) / base_volume
if ratio <= 0:
return 0
return int(math.ceil(ratio))
def _update_trailing(self, candle):
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
high = float(candle.HighPrice)
stop_distance = float(self.StopLossPoints) * self._price_step
trailing_distance = float(self.TrailingStopPoints) * self._price_step
if self.Position > 0 and self._long_entry_price is not None:
entry_long = self._long_entry_price
if stop_distance > 0 and low <= entry_long - stop_distance:
self.SellMarket(float(Math.Abs(self.Position)))
return
if trailing_distance > 0 and close - entry_long >= trailing_distance:
desired_stop = close - trailing_distance
if self._long_trailing_stop is None or desired_stop > self._long_trailing_stop:
self._long_trailing_stop = desired_stop
self._try_reduce_long_position()
if self._long_trailing_stop is not None and low <= self._long_trailing_stop:
self.SellMarket(float(Math.Abs(self.Position)))
elif self.Position < 0 and self._short_entry_price is not None:
entry_short = self._short_entry_price
position_volume = float(Math.Abs(self.Position))
if stop_distance > 0 and high >= entry_short + stop_distance:
self.BuyMarket(position_volume)
return
if trailing_distance > 0 and entry_short - close >= trailing_distance:
desired_stop = close + trailing_distance
if self._short_trailing_stop is None or desired_stop < self._short_trailing_stop:
self._short_trailing_stop = desired_stop
self._try_reduce_short_position()
if self._short_trailing_stop is not None and high >= self._short_trailing_stop:
self.BuyMarket(position_volume)
def _try_reduce_long_position(self):
if not self.UsePartialClose:
return
if self.Position <= 0:
return
position_volume = float(Math.Abs(self.Position))
half = position_volume / 2.0
normalized_half = self._normalize_exit_volume(half, position_volume)
if self._min_volume_limit > 0 and normalized_half < self._min_volume_limit:
self.SellMarket(position_volume)
return
if normalized_half > 0:
self.SellMarket(normalized_half)
def _try_reduce_short_position(self):
if not self.UsePartialClose:
return
if self.Position >= 0:
return
position_volume = float(Math.Abs(self.Position))
half = position_volume / 2.0
normalized_half = self._normalize_exit_volume(half, position_volume)
if self._min_volume_limit > 0 and normalized_half < self._min_volume_limit:
self.BuyMarket(position_volume)
return
if normalized_half > 0:
self.BuyMarket(normalized_half)
def OnReseted(self):
super(open_tiks_strategy, self).OnReseted()
self._price_step = 1.0
self._volume_step = 0.0
self._min_volume_limit = 0.0
self._max_volume_limit = 0.0
self._high1 = None
self._high2 = None
self._high3 = None
self._open1 = None
self._open2 = None
self._open3 = None
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
def CreateClone(self):
return open_tiks_strategy()