Adaptive Trail sample

The source for Adaptive Trail

Adaptive Trail description

Everyone knows most broker tools contain trailing stop function by default. You just give trail amount and broker takes care of the rest. But it also reveals your stop price, exposes you to stop hunting and is fixed unless you modify your order.

Implementing your own trailing function will give you freedom to customize and adjust your stops dynamically based on price action or parameters you choose. Added bonus is that you avoid revealing your stop price because nothing is sent from your computer to your broker until stop criteria is fulfilled.

We start in function where we handle order fill from broker for position opening. Our opening price becomes current lowest price in position and highest price in position. We store that value to corresponding variables. Likewise when position is closed we set lowest price in position and highest price in position variable to NaN representing an undefined value as there is no position to track anymore.

#region OrderFilled
public override void OrderFilled(Position position, Trade trade)
{
    if ( trade.TradeType == TradeType.OpenPosition )
    {
        _highestPriceInPosition = position.EntryPrice.SymbolPrice;
        _lowestPriceInPosition = position.EntryPrice.SymbolPrice;
    }
    if ( trade.TradeType == TradeType.ClosePosition ||
        trade.TradeType == TradeType.StopLoss ||
        trade.TradeType == TradeType.ProfitTarget )
    {
        _highestPriceInPosition = double.NaN;
        _lowestPriceInPosition = double.NaN;
    }
}
#endregion

The business logic happens in reception of new tick.

First we check if new tick is higher than current high in position. If yes, we update highest price for position. Same logic for lowest price.

Next we check received tick price against highest/lowest price in position. If we get a tick price against our position we send sell or cover order to broker to bank our profits. In example below we use fixed 8 pips criteria. Notice we use SubmitOrder method instead of ClosePosition which allows us to modify stake size without closing whole trade.


#region NewTick
public override void NewTick(BarData partialBar, TickData tick)
{
    IList pos = SymbolScript.SystemData.PositionManager.GetOpenPositions(Symbol);
	
    if ( pos.Count > 0 )
    {
        if ( tick.price > _highestPriceInPosition )
            _highestPriceInPosition = tick.price;
        if ( tick.price < _lowestPriceInPosition )
            _lowestPriceInPosition = tick.price;
	
        List pendorders = 
            SymbolScript.SystemData.PositionManager.GetPendingOrdersForPosition(pos[0].ID);

        if ( pos[0].Type == PositionType.Long )
        {
            if ( tick.price < ( _highestPriceInPosition - 8 * _tickSize ) )
            {
                OrderSettings _order = new OrderSettings();
                _order.TransactionType = TransactionType.Sell;
                if ( pendorders.Count == 0 )
                    pos[0].SubmitOrder(_order);
            }
        }
        if ( pos[0].Type == PositionType.Short )
        {
            if ( tick.price > ( _lowestPriceInPosition + 8 * _tickSize ) )
            {
                OrderSettings _order = new OrderSettings();
                _order.TransactionType = TransactionType.Cover;
                if ( pendorders.Count == 0 )
                    pos[0].SubmitOrder(_order);
            }
        }
    }
} // NewTick
#endregion

Now think of replacing the fixed 8 pips with your own function. In your function you could use volatility, standard deviation, rate of change, pips gained or even machine learning to calculate return value for your adaptive custom trail depending on market situation or position status. You could even use Random method to salt your return value to enable a bit variation in your stops (in pre-determined range, otherwise backtesting becomes impossible). As a result nobody can know the exact place of your stop as nothing is sent to broker until SubmitOrder method is called. And you can even call your function for every tick received to calculate new trail price. But be aware that your algo becomes computationally expensive if you do that for every tick.

Finally, here is an example of such function. By default we return 8 pips. But if we have gained 30 pips we start coupling our trail to ATR, in this example 2 times current ATR. This function makes our trail adaptive not only to pips gained but future ATR changes too.


public double _trailCalc()
{
    IList pos = SymbolScript.SystemData.PositionManager.GetOpenPositions(Symbol);

    if ( pos.Count > 0 )
    {		
        if ( pos[0].Type == PositionType.Long )
        {
            if ( ( _highestPriceInPosition - pos[0].EntryPrice.SymbolPrice ) > 30 * _tickSize )
            {
                return 2 * _atr.Current;
            }
        }
    }
    else
    {		
        if ( pos[0].Type == PositionType.Short )
        {
            if ( ( pos[0].EntryPrice.SymbolPrice - _lowestPriceInPosition ) > 30 * _tickSize )
            {
                return 2 * _atr.Current;
            }
        }
    }	
    return 8; //default return value
}