Стратегия Absorption
Стратегия повторяет работу советника Absorption для MetaTrader. Алгоритм ищет свечи-поглощения, которые перекрывают диапазон предыдущей свечи и одновременно формируют локальный экстремум в пределах короткой истории. Как только найден подходящий бар, выставляется пара встречных стоп-заявок и далее позиция сопровождается фиксированной целью, переводом в безубыток и трейлинг-стопом.
Логика торговли
- Поиск паттерна
- Анализируются две последние завершённые свечи.
- Свеча считается поглощением, если её максимум выше максимума предыдущей свечи, а минимум ниже предыдущего минимума.
- Дополнительно проверяется, что максимум или минимум этой свечи является экстремумом среди последних
MaxSearchбаров. - Приоритет отдаётся более старой свече (две свечи назад). Если условия выполняют обе свечи, используется старшая; иначе допускается сигнал по последней свече.
- Выставление ордеров
- Бай-стоп размещается на уровне максимума свечи плюс заданный
Indent. - Селл-стоп размещается на уровне минимума свечи минус тот же
Indent. - Оба ордера используют общий объём стратегии.
- Для каждой отложенной заявки сохраняются уровни стопа и, при необходимости, тейк-профита. Если ордера не активированы, они отменяются через
OrderExpirationHoursчасов.
- Бай-стоп размещается на уровне максимума свечи плюс заданный
- Сопровождение позиции
- При исполнении одной заявки противоположная отменяется.
- Начальный стоп ставится за противоположной границей свечи-поглощения с учётом отступа.
- Фиксированный тейк-профит закрывает сделку после достижения заданного расстояния в шагах цены.
- Модуль безубытка переносит стоп на
Entry + Breakeven(лонг) илиEntry - Breakeven(шорт) после набора прибыли вBreakevenProfitшагов. - Трейлинг-стоп удерживает стоп-лосс на расстоянии
TrailingStopот лучшей цены и срабатывает только после движения минимум наTrailingStepшагов в прибыльную сторону.
Параметры
| Параметр | Описание |
|---|---|
CandleType |
Тип используемых свечей (по умолчанию часовые). |
MaxSearch |
Количество недавних свечей для подтверждения экстремумов. |
TakeProfitBuy |
Дистанция тейк-профита для лонга в шагах цены. 0 отключает цель. |
TakeProfitSell |
Дистанция тейк-профита для шорта в шагах цены. 0 отключает цель. |
TrailingStop |
Размер трейлинг-стопа в шагах. 0 отключает сопровождение. |
TrailingStep |
Минимальное продвижение для обновления трейлинг-стопа. Должно быть положительным при включённом трейлинге. |
Indent |
Отступ в шагах цены от экстремумов свечи для постановки стоп-заявок. |
OrderExpirationHours |
Срок действия отложенных ордеров. По истечении этого времени они отменяются. |
Breakeven |
Отступ стоп-лосса после перевода в безубыток. 0 отключает модуль. |
BreakevenProfit |
Прибыль в шагах цены, необходимая для активации перевода в безубыток. |
Все расстояния задаются в шагах цены инструмента. Базовый объём стратегии установлен на 0.1.
Управление рисками
Выходы из позиции выполняются рыночными ордерами. Правила стоп-лосса, тейк-профита, безубытка и трейлинг-стопа отслеживают максимумы и минимумы свечей, чтобы фиксировать внутридневные касания уровней. После отправки ордера на выход новые заявки не формируются до тех пор, пока позиция не станет нулевой.
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>
/// Absorption outside bar breakout strategy.
/// Detects engulfing candles near recent extremes and places stop orders around the pattern.
/// </summary>
public class AbsorptionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maxSearch;
private readonly StrategyParam<decimal> _takeProfitBuy;
private readonly StrategyParam<decimal> _takeProfitSell;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<decimal> _indent;
private readonly StrategyParam<int> _orderExpirationHours;
private readonly StrategyParam<decimal> _breakeven;
private readonly StrategyParam<decimal> _breakevenProfit;
private Highest _highest;
private Lowest _lowest;
private ICandleMessage _prev1;
private ICandleMessage _prev2;
private bool _hasActiveOrders;
private decimal _pendingHigh;
private decimal _pendingLow;
private decimal _pendingBuyPrice;
private decimal _pendingSellPrice;
private decimal _pendingBuyStopLoss;
private decimal _pendingSellStopLoss;
private decimal _pendingBuyTakeProfit;
private decimal _pendingSellTakeProfit;
private DateTimeOffset? _ordersExpiry;
private decimal _entryPrice;
private decimal _stopLoss;
private decimal _takeProfit;
private decimal _prevPosition;
private bool _exitRequestActive;
/// <summary>
/// Candle type to analyze.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of candles to inspect for extreme prices.
/// </summary>
public int MaxSearch
{
get => _maxSearch.Value;
set => _maxSearch.Value = value;
}
/// <summary>
/// Take profit distance for long trades in price steps.
/// </summary>
public decimal TakeProfitBuy
{
get => _takeProfitBuy.Value;
set => _takeProfitBuy.Value = value;
}
/// <summary>
/// Take profit distance for short trades in price steps.
/// </summary>
public decimal TakeProfitSell
{
get => _takeProfitSell.Value;
set => _takeProfitSell.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Minimal step for trailing stop updates in price steps.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Indent distance around the reference candle in price steps.
/// </summary>
public decimal Indent
{
get => _indent.Value;
set => _indent.Value = value;
}
/// <summary>
/// Pending order expiration in hours.
/// </summary>
public int OrderExpirationHours
{
get => _orderExpirationHours.Value;
set => _orderExpirationHours.Value = value;
}
/// <summary>
/// Distance to move stop-loss to breakeven in price steps.
/// </summary>
public decimal Breakeven
{
get => _breakeven.Value;
set => _breakeven.Value = value;
}
/// <summary>
/// Profit needed before breakeven activation in price steps.
/// </summary>
public decimal BreakevenProfit
{
get => _breakevenProfit.Value;
set => _breakevenProfit.Value = value;
}
/// <summary>
/// Initializes <see cref="AbsorptionStrategy"/>.
/// </summary>
public AbsorptionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to process", "General");
_maxSearch = Param(nameof(MaxSearch), 10)
.SetGreaterThanZero()
.SetDisplay("Search Depth", "Bars to inspect for extremes", "Pattern");
_takeProfitBuy = Param(nameof(TakeProfitBuy), 10m)
.SetNotNegative()
.SetDisplay("Long TP", "Take profit for long trades (steps)", "Risk");
_takeProfitSell = Param(nameof(TakeProfitSell), 10m)
.SetNotNegative()
.SetDisplay("Short TP", "Take profit for short trades (steps)", "Risk");
_trailingStop = Param(nameof(TrailingStop), 5m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance (steps)", "Risk");
_trailingStep = Param(nameof(TrailingStep), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step", "Minimal move to update trailing stop (steps)", "Risk");
_indent = Param(nameof(Indent), 1m)
.SetNotNegative()
.SetDisplay("Indent", "Offset from high/low for entries (steps)", "Pattern");
_orderExpirationHours = Param(nameof(OrderExpirationHours), 8)
.SetGreaterThanZero()
.SetDisplay("Order Expiration", "Validity of pending orders in hours", "Pattern");
_breakeven = Param(nameof(Breakeven), 1m)
.SetNotNegative()
.SetDisplay("Breakeven", "Stop offset once breakeven triggers (steps)", "Risk");
_breakevenProfit = Param(nameof(BreakevenProfit), 10m)
.SetNotNegative()
.SetDisplay("Breakeven Profit", "Profit needed before moving to breakeven (steps)", "Risk");
Volume = 0.1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prev1 = null;
_prev2 = null;
_hasActiveOrders = false;
_ordersExpiry = null;
_pendingHigh = 0m;
_pendingLow = 0m;
_pendingBuyPrice = 0m;
_pendingSellPrice = 0m;
_pendingBuyStopLoss = 0m;
_pendingSellStopLoss = 0m;
_pendingBuyTakeProfit = 0m;
_pendingSellTakeProfit = 0m;
_entryPrice = 0m;
_stopLoss = 0m;
_takeProfit = 0m;
_prevPosition = 0m;
_exitRequestActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStop > 0m && TrailingStep <= 0m)
throw new InvalidOperationException("Trailing step must be positive when trailing stop is enabled.");
if (Breakeven > 0m)
{
if (BreakevenProfit <= 0m)
throw new InvalidOperationException("Breakeven profit must be positive when breakeven is enabled.");
if (BreakevenProfit <= Breakeven)
throw new InvalidOperationException("Breakeven profit must exceed breakeven distance.");
}
_highest = new Highest { Length = MaxSearch };
_lowest = new Lowest { Length = MaxSearch };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandleRaw)
.Start();
}
private void ProcessCandleRaw(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highResult = _highest.Process(candle);
var lowResult = _lowest.Process(candle);
if (highResult.IsEmpty || lowResult.IsEmpty || !_highest.IsFormed || !_lowest.IsFormed)
{
UpdatePreviousCandles(candle);
_prevPosition = Position;
return;
}
var highestValue = highResult.ToDecimal();
var lowestValue = lowResult.ToDecimal();
ManageActivePosition(candle);
// Check if pending breakout orders should be triggered
if (_hasActiveOrders)
{
if (_ordersExpiry.HasValue && candle.CloseTime >= _ordersExpiry.Value)
{
ClearPendingOrders();
}
else
{
TryTriggerPendingOrders(candle);
}
}
if (Position == 0 && !_hasActiveOrders && _prev1 != null && _prev2 != null)
{
TryPlaceOrders(candle, highestValue, lowestValue);
}
UpdatePreviousCandles(candle);
if (Position != 0 && _hasActiveOrders)
{
ClearPendingOrders();
}
_prevPosition = Position;
}
private void TryTriggerPendingOrders(ICandleMessage candle)
{
if (Position != 0)
return;
// Check if price has broken above the pending buy level
if (_pendingBuyPrice > 0 && candle.HighPrice >= _pendingBuyPrice)
{
BuyMarket(Volume);
_entryPrice = _pendingBuyPrice;
_stopLoss = _pendingBuyStopLoss;
_takeProfit = _pendingBuyTakeProfit;
_exitRequestActive = false;
ClearPendingOrders();
return;
}
// Check if price has broken below the pending sell level
if (_pendingSellPrice > 0 && candle.LowPrice <= _pendingSellPrice)
{
SellMarket(Volume);
_entryPrice = _pendingSellPrice;
_stopLoss = _pendingSellStopLoss;
_takeProfit = _pendingSellTakeProfit;
_exitRequestActive = false;
ClearPendingOrders();
}
}
private void TryPlaceOrders(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
var prev2Outside = _prev2.HighPrice > _prev1.HighPrice && _prev2.LowPrice < _prev1.LowPrice;
var prev1Outside = _prev1.HighPrice > _prev2.HighPrice && _prev1.LowPrice < _prev2.LowPrice;
var prev2IsExtreme = IsLowestBar(_prev2, _prev1, candle, lowestValue) || IsHighestBar(_prev2, _prev1, candle, highestValue);
var prev1IsExtreme = IsLowestBar(_prev1, _prev2, candle, lowestValue) || IsHighestBar(_prev1, _prev2, candle, highestValue);
if (prev2Outside && prev2IsExtreme)
{
PlaceEntryOrders(_prev2, candle);
}
else if (prev1Outside && prev1IsExtreme)
{
PlaceEntryOrders(_prev1, candle);
}
}
private void PlaceEntryOrders(ICandleMessage patternCandle, ICandleMessage currentCandle)
{
var volume = Volume;
if (volume <= 0m)
return;
var indent = GetPriceOffset(Indent);
var step = Security?.PriceStep ?? 0.0001m;
var buyPrice = patternCandle.HighPrice + indent;
var sellPrice = patternCandle.LowPrice - indent;
if (sellPrice <= 0m)
sellPrice = step;
var buyStopLoss = Math.Max(patternCandle.LowPrice - indent, step);
var sellStopLoss = patternCandle.HighPrice + indent;
var buyTakeOffset = GetPriceOffset(TakeProfitBuy);
var sellTakeOffset = GetPriceOffset(TakeProfitSell);
var buyTakeProfit = buyTakeOffset > 0m ? buyPrice + buyTakeOffset : 0m;
var sellTakeProfit = sellTakeOffset > 0m ? sellPrice - sellTakeOffset : 0m;
// Store pending breakout levels (will be triggered on next candle)
_hasActiveOrders = true;
_pendingHigh = patternCandle.HighPrice;
_pendingLow = patternCandle.LowPrice;
_pendingBuyPrice = buyPrice;
_pendingSellPrice = sellPrice;
_pendingBuyStopLoss = buyStopLoss;
_pendingSellStopLoss = sellStopLoss;
_pendingBuyTakeProfit = buyTakeProfit;
_pendingSellTakeProfit = sellTakeProfit;
_exitRequestActive = false;
_ordersExpiry = OrderExpirationHours > 0
? currentCandle.CloseTime + TimeSpan.FromHours(OrderExpirationHours)
: null;
}
private void ManageActivePosition(ICandleMessage candle)
{
if (_exitRequestActive)
return;
if (Position > 0)
{
UpdateBreakevenLong(candle);
UpdateTrailingLong(candle);
if (_stopLoss > 0m && candle.LowPrice <= _stopLoss)
{
SellMarket(Math.Abs(Position));
_exitRequestActive = true;
return;
}
if (_takeProfit > 0m && candle.HighPrice >= _takeProfit)
{
SellMarket(Math.Abs(Position));
_exitRequestActive = true;
}
}
else if (Position < 0)
{
UpdateBreakevenShort(candle);
UpdateTrailingShort(candle);
if (_stopLoss > 0m && candle.HighPrice >= _stopLoss)
{
BuyMarket(Math.Abs(Position));
_exitRequestActive = true;
return;
}
if (_takeProfit > 0m && candle.LowPrice <= _takeProfit)
{
BuyMarket(Math.Abs(Position));
_exitRequestActive = true;
}
}
}
private void UpdateBreakevenLong(ICandleMessage candle)
{
if (Breakeven <= 0m || BreakevenProfit <= 0m)
return;
if (_stopLoss >= _entryPrice + GetPriceOffset(Breakeven))
return;
if (candle.HighPrice - _entryPrice >= GetPriceOffset(BreakevenProfit))
_stopLoss = _entryPrice + GetPriceOffset(Breakeven);
}
private void UpdateBreakevenShort(ICandleMessage candle)
{
if (Breakeven <= 0m || BreakevenProfit <= 0m)
return;
if (_stopLoss <= _entryPrice - GetPriceOffset(Breakeven))
return;
if (_entryPrice - candle.LowPrice >= GetPriceOffset(BreakevenProfit))
_stopLoss = _entryPrice - GetPriceOffset(Breakeven);
}
private void UpdateTrailingLong(ICandleMessage candle)
{
if (TrailingStop <= 0m)
return;
var trailing = GetPriceOffset(TrailingStop);
var step = GetPriceOffset(TrailingStep);
var current = candle.HighPrice;
if (current - _entryPrice <= trailing + step)
return;
if (_stopLoss < current - (trailing + step))
_stopLoss = Math.Max(_stopLoss, current - trailing);
}
private void UpdateTrailingShort(ICandleMessage candle)
{
if (TrailingStop <= 0m)
return;
var trailing = GetPriceOffset(TrailingStop);
var step = GetPriceOffset(TrailingStep);
var current = candle.LowPrice;
if (_entryPrice - current <= trailing + step)
return;
if (_stopLoss == 0m || _stopLoss > current + trailing + step)
_stopLoss = current + trailing;
}
private void UpdatePreviousCandles(ICandleMessage candle)
{
_prev2 = _prev1;
_prev1 = candle;
}
private void ClearPendingOrders()
{
_hasActiveOrders = false;
_ordersExpiry = null;
_pendingHigh = 0m;
_pendingLow = 0m;
_pendingBuyPrice = 0m;
_pendingSellPrice = 0m;
_pendingBuyStopLoss = 0m;
_pendingSellStopLoss = 0m;
_pendingBuyTakeProfit = 0m;
_pendingSellTakeProfit = 0m;
}
private bool IsLowestBar(ICandleMessage candidate, ICandleMessage other, ICandleMessage current, decimal lowestValue)
{
if (!AreClose(candidate.LowPrice, lowestValue))
return false;
return candidate.LowPrice < other.LowPrice && candidate.LowPrice < current.LowPrice;
}
private bool IsHighestBar(ICandleMessage candidate, ICandleMessage other, ICandleMessage current, decimal highestValue)
{
if (!AreClose(candidate.HighPrice, highestValue))
return false;
return candidate.HighPrice > other.HighPrice && candidate.HighPrice > current.HighPrice;
}
private decimal GetPriceOffset(decimal value)
{
var step = Security?.PriceStep ?? 0.0001m;
return value * step;
}
private bool AreClose(decimal first, decimal second)
{
var tolerance = (Security?.PriceStep ?? 0.0001m) / 2m;
return Math.Abs(first - second) <= tolerance;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import Highest, Lowest, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class absorption_strategy(Strategy):
def __init__(self):
super(absorption_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._max_search = self.Param("MaxSearch", 10)
self._take_profit_buy = self.Param("TakeProfitBuy", 10.0)
self._take_profit_sell = self.Param("TakeProfitSell", 10.0)
self._trailing_stop = self.Param("TrailingStop", 5.0)
self._trailing_step = self.Param("TrailingStep", 5.0)
self._indent = self.Param("Indent", 1.0)
self._order_expiration_hours = self.Param("OrderExpirationHours", 8)
self._breakeven = self.Param("Breakeven", 1.0)
self._breakeven_profit = self.Param("BreakevenProfit", 10.0)
self._highest = None
self._lowest = None
self._prev1 = None
self._prev2 = None
self._has_active_orders = False
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
self._orders_expiry = None
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
self._prev_position = 0.0
self._exit_request_active = False
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(absorption_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self._max_search.Value
self._lowest = Lowest()
self._lowest.Length = self._max_search.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ_h = CandleIndicatorValue(self._highest, candle)
civ_h.IsFinal = True
high_result = self._highest.Process(civ_h)
civ_l = CandleIndicatorValue(self._lowest, candle)
civ_l.IsFinal = True
low_result = self._lowest.Process(civ_l)
if high_result.IsEmpty or low_result.IsEmpty or not self._highest.IsFormed or not self._lowest.IsFormed:
self._update_previous_candles(candle)
self._prev_position = self.Position
return
highest_value = float(high_result.Value)
lowest_value = float(low_result.Value)
self._manage_active_position(candle)
if self._has_active_orders:
if self._orders_expiry is not None and candle.CloseTime >= self._orders_expiry:
self._clear_pending_orders()
else:
self._try_trigger_pending_orders(candle)
if self.Position == 0 and not self._has_active_orders and self._prev1 is not None and self._prev2 is not None:
self._try_place_orders(candle, highest_value, lowest_value)
self._update_previous_candles(candle)
if self.Position != 0 and self._has_active_orders:
self._clear_pending_orders()
self._prev_position = self.Position
def _try_trigger_pending_orders(self, candle):
if self.Position != 0:
return
if self._pending_buy_price > 0 and float(candle.HighPrice) >= self._pending_buy_price:
self.BuyMarket(self.Volume)
self._entry_price = self._pending_buy_price
self._stop_loss = self._pending_buy_stop_loss
self._take_profit = self._pending_buy_take_profit
self._exit_request_active = False
self._clear_pending_orders()
return
if self._pending_sell_price > 0 and float(candle.LowPrice) <= self._pending_sell_price:
self.SellMarket(self.Volume)
self._entry_price = self._pending_sell_price
self._stop_loss = self._pending_sell_stop_loss
self._take_profit = self._pending_sell_take_profit
self._exit_request_active = False
self._clear_pending_orders()
def _try_place_orders(self, candle, highest_value, lowest_value):
prev2_high = float(self._prev2.HighPrice)
prev2_low = float(self._prev2.LowPrice)
prev1_high = float(self._prev1.HighPrice)
prev1_low = float(self._prev1.LowPrice)
cur_high = float(candle.HighPrice)
cur_low = float(candle.LowPrice)
prev2_outside = prev2_high > prev1_high and prev2_low < prev1_low
prev1_outside = prev1_high > prev2_high and prev1_low < prev2_low
prev2_is_extreme = (self._is_lowest_bar(prev2_low, prev1_low, cur_low, lowest_value) or
self._is_highest_bar(prev2_high, prev1_high, cur_high, highest_value))
prev1_is_extreme = (self._is_lowest_bar(prev1_low, prev2_low, cur_low, lowest_value) or
self._is_highest_bar(prev1_high, prev2_high, cur_high, highest_value))
if prev2_outside and prev2_is_extreme:
self._place_entry_orders(self._prev2, candle)
elif prev1_outside and prev1_is_extreme:
self._place_entry_orders(self._prev1, candle)
def _place_entry_orders(self, pattern_candle, current_candle):
volume = float(self.Volume)
if volume <= 0:
return
indent = self._get_price_offset(self._indent.Value)
step = self._get_price_step()
buy_price = float(pattern_candle.HighPrice) + indent
sell_price = float(pattern_candle.LowPrice) - indent
if sell_price <= 0:
sell_price = step
buy_stop_loss = max(float(pattern_candle.LowPrice) - indent, step)
sell_stop_loss = float(pattern_candle.HighPrice) + indent
buy_take_offset = self._get_price_offset(self._take_profit_buy.Value)
sell_take_offset = self._get_price_offset(self._take_profit_sell.Value)
buy_take_profit = buy_price + buy_take_offset if buy_take_offset > 0 else 0.0
sell_take_profit = sell_price - sell_take_offset if sell_take_offset > 0 else 0.0
self._has_active_orders = True
self._pending_high = float(pattern_candle.HighPrice)
self._pending_low = float(pattern_candle.LowPrice)
self._pending_buy_price = buy_price
self._pending_sell_price = sell_price
self._pending_buy_stop_loss = buy_stop_loss
self._pending_sell_stop_loss = sell_stop_loss
self._pending_buy_take_profit = buy_take_profit
self._pending_sell_take_profit = sell_take_profit
self._exit_request_active = False
if self._order_expiration_hours.Value > 0:
self._orders_expiry = current_candle.CloseTime + TimeSpan.FromHours(self._order_expiration_hours.Value)
else:
self._orders_expiry = None
def _manage_active_position(self, candle):
if self._exit_request_active:
return
if self.Position > 0:
self._update_breakeven_long(candle)
self._update_trailing_long(candle)
if self._stop_loss > 0 and float(candle.LowPrice) <= self._stop_loss:
self.SellMarket(abs(self.Position))
self._exit_request_active = True
return
if self._take_profit > 0 and float(candle.HighPrice) >= self._take_profit:
self.SellMarket(abs(self.Position))
self._exit_request_active = True
elif self.Position < 0:
self._update_breakeven_short(candle)
self._update_trailing_short(candle)
if self._stop_loss > 0 and float(candle.HighPrice) >= self._stop_loss:
self.BuyMarket(abs(self.Position))
self._exit_request_active = True
return
if self._take_profit > 0 and float(candle.LowPrice) <= self._take_profit:
self.BuyMarket(abs(self.Position))
self._exit_request_active = True
def _update_breakeven_long(self, candle):
if self._breakeven.Value <= 0 or self._breakeven_profit.Value <= 0:
return
be_offset = self._get_price_offset(self._breakeven.Value)
if self._stop_loss >= self._entry_price + be_offset:
return
be_profit_offset = self._get_price_offset(self._breakeven_profit.Value)
if float(candle.HighPrice) - self._entry_price >= be_profit_offset:
self._stop_loss = self._entry_price + be_offset
def _update_breakeven_short(self, candle):
if self._breakeven.Value <= 0 or self._breakeven_profit.Value <= 0:
return
be_offset = self._get_price_offset(self._breakeven.Value)
if self._stop_loss <= self._entry_price - be_offset:
return
be_profit_offset = self._get_price_offset(self._breakeven_profit.Value)
if self._entry_price - float(candle.LowPrice) >= be_profit_offset:
self._stop_loss = self._entry_price - be_offset
def _update_trailing_long(self, candle):
if self._trailing_stop.Value <= 0:
return
trailing = self._get_price_offset(self._trailing_stop.Value)
step = self._get_price_offset(self._trailing_step.Value)
current = float(candle.HighPrice)
if current - self._entry_price <= trailing + step:
return
if self._stop_loss < current - (trailing + step):
self._stop_loss = max(self._stop_loss, current - trailing)
def _update_trailing_short(self, candle):
if self._trailing_stop.Value <= 0:
return
trailing = self._get_price_offset(self._trailing_stop.Value)
step = self._get_price_offset(self._trailing_step.Value)
current = float(candle.LowPrice)
if self._entry_price - current <= trailing + step:
return
if self._stop_loss == 0 or self._stop_loss > current + trailing + step:
self._stop_loss = current + trailing
def _update_previous_candles(self, candle):
self._prev2 = self._prev1
self._prev1 = candle
def _clear_pending_orders(self):
self._has_active_orders = False
self._orders_expiry = None
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
def _is_lowest_bar(self, candidate_low, other_low, current_low, lowest_value):
if not self._are_close(candidate_low, lowest_value):
return False
return candidate_low < other_low and candidate_low < current_low
def _is_highest_bar(self, candidate_high, other_high, current_high, highest_value):
if not self._are_close(candidate_high, highest_value):
return False
return candidate_high > other_high and candidate_high > current_high
def _get_price_offset(self, value):
step = self._get_price_step()
return value * step
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
return step if step > 0 else 0.0001
def _are_close(self, first, second):
tolerance = self._get_price_step() / 2.0
return abs(first - second) <= tolerance
def OnReseted(self):
super(absorption_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._prev1 = None
self._prev2 = None
self._has_active_orders = False
self._orders_expiry = None
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
self._prev_position = 0.0
self._exit_request_active = False
def CreateClone(self):
return absorption_strategy()