The Karacatica strategy is a trend-following approach that combines price action with the Average Directional Index (ADX). It looks for situations where the current close price is higher or lower than the close price a specified number of candles ago and confirms the move with the dominance of the +DI or -DI line.
Indicators
Average Directional Index (ADX) – measures trend strength and provides +DI and -DI components.
Price Comparison – checks whether the latest close is above or below the close from Period candles back.
Parameters
Period – number of candles used for both the ADX calculation and the lookback for the price comparison. Default is 70.
TakeProfitPercent – take-profit expressed as a percentage of the entry price. Default is 2%.
StopLossPercent – stop-loss expressed as a percentage of the entry price. Default is 1%.
CandleType – timeframe of candles to subscribe to. Default is 1 hour.
Trading Logic
Long Entry: Close > Close[Period] and +DI > -DI with no existing long signal. Closes short positions and opens a long one.
Short Entry: Close < Close[Period] and -DI > +DI with no existing short signal. Closes long positions and opens a short one.
Position Protection: StartProtection applies both take-profit and stop-loss percentages.
Usage Notes
Designed for StockSharp high-level API; it subscribes to candles and binds the ADX indicator.
The strategy automatically closes opposite positions when a new signal appears.
No Python implementation is provided for now.
Disclaimer
This example is for educational purposes only and does not guarantee profits. Trading involves significant risk. Always test strategies thoroughly before deploying them on live markets.
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>
/// Karacatica strategy uses ADX to determine trend direction and compares
/// current close price with the close price from a specified period ago.
/// It goes long when an uptrend is detected and price is rising, and
/// goes short when a downtrend is detected and price is falling.
/// </summary>
public class KaracaticaStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<DataType> _candleType;
private AverageDirectionalIndex _adx;
private readonly Queue<decimal> _closeQueue = new();
private int _lastSignal;
/// <summary>
/// Indicator period used for ADX and price comparison.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public KaracaticaStrategy()
{
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("Period", "ADX period and lookback for close comparison", "Indicators")
.SetOptimize(20, 50, 10);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = null;
_closeQueue.Clear();
_lastSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_adx = new AverageDirectionalIndex { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _adx);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
_closeQueue.Enqueue(candle.ClosePrice);
if (_closeQueue.Count > Period + 1)
_closeQueue.Dequeue();
if (_closeQueue.Count <= Period)
return;
if (!adxValue.IsFormed)
return;
var pastClose = _closeQueue.Peek();
var adxTyped = adxValue as IAverageDirectionalIndexValue;
if (adxTyped?.Dx is not IDirectionalIndexValue dxVal)
return;
var plusDi = dxVal.Plus;
var minusDi = dxVal.Minus;
if (plusDi is null || minusDi is null)
return;
var buySignal = candle.ClosePrice > pastClose && plusDi > minusDi && _lastSignal != 1;
var sellSignal = candle.ClosePrice < pastClose && minusDi > plusDi && _lastSignal != -1;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (buySignal && Position <= 0)
{
BuyMarket();
_lastSignal = 1;
}
else if (sellSignal && Position >= 0)
{
SellMarket();
_lastSignal = -1;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class karacatica_strategy(Strategy):
def __init__(self):
super(karacatica_strategy, self).__init__()
self._period = self.Param("Period", 30) \
.SetDisplay("Period", "ADX period and lookback for close comparison", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._close_queue = []
self._last_signal = 0
@property
def period(self):
return self._period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(karacatica_strategy, self).OnReseted()
self._close_queue = []
self._last_signal = 0
def OnStarted2(self, time):
super(karacatica_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self.period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawOwnTrades(area)
def process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._close_queue.append(close)
if len(self._close_queue) > self.period + 1:
self._close_queue.pop(0)
if len(self._close_queue) <= self.period:
return
if not adx_value.IsFormed:
return
past_close = self._close_queue[0]
plus_di = adx_value.Dx.Plus
minus_di = adx_value.Dx.Minus
if plus_di is None or minus_di is None:
return
plus_di = float(plus_di)
minus_di = float(minus_di)
buy_signal = close > past_close and plus_di > minus_di and self._last_signal != 1
sell_signal = close < past_close and minus_di > plus_di and self._last_signal != -1
if buy_signal and self.Position <= 0:
self.BuyMarket()
self._last_signal = 1
elif sell_signal and self.Position >= 0:
self.SellMarket()
self._last_signal = -1
def CreateClone(self):
return karacatica_strategy()