Absorption Strategy
The strategy replicates the Absorption expert advisor for MetaTrader. It searches for "engulfing" candles that absorb the range of the previous bar and form an extreme within a short lookback. When such an absorption bar appears, the algorithm brackets the market with stop orders and manages the resulting position with a mix of fixed targets, breakeven logic, and a trailing stop.
Trading logic
- Pattern detection
- Inspect the last two completed candles.
- Treat a candle as an absorption bar when its high is above the previous candle high and its low is below the previous candle low.
- Validate the bar by checking whether its high or low is the most extreme value within the last
MaxSearchcandles. - Give priority to the older candle (two bars ago). If both bars satisfy the absorption condition, the older bar is used; otherwise the most recent bar may trigger the setup.
- Order placement
- Place a buy stop order at the bar high plus the configured
Indent. - Place a sell stop order at the bar low minus the same
Indent. - Both orders use the common strategy volume.
- Each pending order stores its own protective stop level and optional take-profit target. Orders automatically expire after
OrderExpirationHoursif they remain unfilled.
- Place a buy stop order at the bar high plus the configured
- Position management
- When one side is filled the opposite pending order is cancelled.
- The initial stop is located at the opposite side of the absorption candle minus/plus the indent.
- An optional fixed take profit closes the trade once the configured distance in price steps is reached.
- The breakeven module moves the stop-loss to
Entry + Breakeven(long) orEntry - Breakeven(short) after the price advances byBreakevenProfitsteps. - The trailing stop keeps the stop-loss at
TrailingStopdistance from the best price, updating only when the price moves by at leastTrailingStepsteps in the profitable direction.
Parameters
| Parameter | Description |
|---|---|
CandleType |
Candle data type to subscribe to (default: 1-hour time frame). |
MaxSearch |
Number of recent candles used to confirm high/low extremes. |
TakeProfitBuy |
Distance in price steps for the long take-profit order. 0 disables the target. |
TakeProfitSell |
Distance in price steps for the short take-profit order. 0 disables the target. |
TrailingStop |
Trailing stop distance in price steps. 0 disables trailing. |
TrailingStep |
Minimum forward move required before the trailing stop is advanced. Must be positive when trailing is enabled. |
Indent |
Offset in price steps that is added above/below the absorption bar to define stop entry levels. |
OrderExpirationHours |
Lifetime of pending orders. After this period the orders are cancelled if not triggered. |
Breakeven |
Offset applied to the stop-loss when the breakeven rule fires. 0 disables breakeven. |
BreakevenProfit |
Profit threshold (in price steps) that must be reached before the stop-loss is moved to breakeven. |
All distance-based inputs are expressed as multiples of the instrument price step. The default strategy volume is set to 0.1.
Risk management
The strategy uses only market orders for exits. Stop-loss, take-profit, breakeven, and trailing rules monitor candle highs and lows to detect intrabar hits. Once an exit order is submitted no additional exit requests are generated until the current position is flat.
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()