Login Page - Create Account

Support Board


Date/Time: Fri, 26 Apr 2024 11:56:33 +0000



[Programming Help] - ACSIL - trailing stop loss and orders

View Count: 2637

[2019-09-16 22:48:44]
MattK - Posts: 42
Hi guys!

I'm working on an automated trading system. Simply put: trying to buy one tick above the previous candle and trail the stop loss one tick under the previous low.

However, it is not behaving as I would like. I'm trying to debug and put messages in the activity log but can't figure out what is wrong with my orders and stop loss.

If you could help me I would love it. When I backtest the system, I end up with trades where the loss is $400 or $5k, etc even though I put a stop loss at 6 ticks under the price.

Thanks in advance.
Matt

-- UPDATED CODE --

// The top of every source code file must include this line
#include "sierrachart.h"

// For reference, refer to this page:
// Advanced Custom Study Interface and Language (ACSIL)

// This line is required. Change the text within the quote
// marks to what you want to name your group of custom studies.
SCDLLName("Reversal strategy")

//This is the basic framework of a study function. Change the name 'TemplateFunction' to what you require.
SCSFExport scsf_ReversalStrategy(SCStudyInterfaceRef sc)
{
  //Define references to the Subgraphs and Inputs for easy reference
  SCSubgraphRef BuyEntrySubgraph = sc.Subgraph[0];
  SCSubgraphRef BuyExitSubgraph = sc.Subgraph[1];
  SCSubgraphRef ATRSubgraph = sc.Subgraph[2];
  SCSubgraphRef EMASubgraph = sc.Subgraph[3];
  
  SCString DebugMessage;
  
  // Section 1 - Set the configuration variables and defaults
  if (sc.SetDefaults)
  {
    sc.GraphName = "Reversal long strategy";

    sc.AutoLoop = 1; //Automatic looping is enabled.
    
    BuyEntrySubgraph.Name = "Buy Entry";
    BuyEntrySubgraph.DrawStyle = DRAWSTYLE_ARROW_UP;
    BuyEntrySubgraph.PrimaryColor = RGB(0, 255, 0);
    BuyEntrySubgraph.LineWidth = 2;
    BuyEntrySubgraph.DrawZeros = false;

    BuyExitSubgraph.Name = "Buy Exit";
    BuyExitSubgraph.DrawStyle = DRAWSTYLE_ARROW_DOWN;
    BuyExitSubgraph.PrimaryColor = RGB(255, 128, 128);
    BuyExitSubgraph.LineWidth = 2;
    BuyExitSubgraph.DrawZeros = false;
    
    sc.Input[0].Name = "Float Input";
    sc.Input[0].SetFloat(0.0f);
    
    return;
  }
  
  // Section 2 - Do data processing here
  float currentPrice = sc.BaseData[SC_LAST][sc.Index];
  float currentHigh = sc.BaseData[SC_HIGH][sc.Index];
  float previousHigh = sc.BaseData[SC_HIGH][sc.Index-1];
  float previous2High = sc.BaseData[SC_HIGH][sc.Index-2];
  float currentLow = sc.BaseData[SC_LOW][sc.Index];
  float previousLow = sc.BaseData[SC_LOW][sc.Index-1];
  float previous2Low = sc.BaseData[SC_LOW][sc.Index-2];
  float previousClose = sc.BaseData[SC_CLOSE][sc.Index-1];
  float previous2Close = sc.BaseData[SC_CLOSE][sc.Index-2];
  float previousHighCloseDiff = sc.BaseData[SC_HIGH][sc.Index-1] - sc.BaseData[SC_CLOSE][sc.Index-1];
  
  // Calculate the Average True Range
  sc.ATR(sc.BaseDataIn, ATRSubgraph, 10, MOVAVGTYPE_SIMPLE);
  float ATR = ATRSubgraph[sc.Index]; // Value of the current ATR
  
  // Calculate the EMA
  sc.ExponentialMovAvg(sc.BaseDataIn[SC_LAST], EMASubgraph, 100);
  float EMA = EMASubgraph[sc.Index]; // Value of the current EMA
  
  s_SCPositionData PositionData;
  sc.GetTradePosition(PositionData);
  
  // Use persistent variables to remember attached order IDs so they can be modified or canceled.
  int& OrderId = sc.GetPersistentInt(1);
  int& Stop1OrderID = sc.GetPersistentInt(2);
  
  float orderPrice = sc.GetPersistentFloat(1);
  float NewPossibleStop = sc.GetPersistentFloat(2);
  float orderStop = sc.GetPersistentFloat(3);
  float currentStop = sc.GetPersistentFloat(4);
  
  int Hour = sc.BaseDateTimeIn[sc.Index].GetHour();
  int Minute = sc.BaseDateTimeIn[sc.Index].GetMinute();
  
  // Data processing once the bar has closed.
  if(sc.GetBarHasClosedStatus()==BHCS_BAR_HAS_CLOSED)
  {
    
    // Cancel the pending order
    if (PositionData.PositionQuantity == 0 && OrderId != 0)
    {
      sc.FlattenAndCancelAllOrders();
      OrderId = 0;
      Stop1OrderID = 0;
    }
    
    if (PositionData.PositionQuantity == 0 && ATR >= 1.8 && currentLow > EMA && currentLow <= previousLow)
    {
      // Mark the signal on the chart
      BuyEntrySubgraph[sc.Index] = sc.Low[sc.Index];
      
      // Create a market order and enter long.
      s_SCNewOrder NewOrder;
      NewOrder.OrderQuantity = 1;
      NewOrder.OrderType = SCT_ORDERTYPE_STOP_LIMIT;
      NewOrder.TimeInForce = SCT_TIF_DAY;
      NewOrder.Price1 = currentHigh + 1*sc.TickSize;
      NewOrder.Price2 = currentHigh + 4*sc.TickSize;
      NewOrder.OCOGroup1Quantity = 1;
      NewOrder.Stop1Price = currentHigh - 3*sc.TickSize;
      
      // This is a common setting and applies to all Stop Attached Orders set on the main order.
      NewOrder.MoveToBreakEven.Type=MOVETO_BE_ACTION_TYPE_OFFSET_TRIGGERED;

      //After 5 ticks of profit, the stop order will be moved to breakeven
      NewOrder.MoveToBreakEven.TriggerOffsetInTicks= 4;
      NewOrder.MoveToBreakEven.BreakEvenLevelOffsetInTicks= 0;
      
      // Using this variable to log a message
      orderPrice = currentHigh + 1*sc.TickSize;
      DebugMessage.Format("Signal at: %f", orderPrice);
      sc.AddMessageToLog(DebugMessage, 1);

      sc.BuyEntry(NewOrder);
      
      // Remember the order ID for subsequent modification and cancellation
      Stop1OrderID = NewOrder.Stop1InternalOrderID;
      OrderId = NewOrder.InternalOrderID;
      
      orderStop = currentHigh - 3*sc.TickSize;
      
    }
    
    // Update the trailing stop loss if the new low is higher than the previous low.
    // Gotta make sure the new stop loss is not lower than the initial one
    NewPossibleStop = currentLow - 2*sc.TickSize;
    
    // COMPARE INITIAL STOP TO NEW STOP IS NOT WORKING RIGHT NOW
    
    if (PositionData.PositionQuantity == 1 && currentLow > previousLow && currentHigh > previousHigh && currentStop < NewPossibleStop && NewPossibleStop > orderStop)
    {
      s_SCTradeOrder ExistingOrder;
      if (sc.GetOrderByOrderID(Stop1OrderID, ExistingOrder) != SCTRADING_ORDER_ERROR)
      {
        
        s_SCNewOrder ModifyOrder;
        ModifyOrder.InternalOrderID = Stop1OrderID;
        ModifyOrder.Price1 = NewPossibleStop;

        sc.ModifyOrder(ModifyOrder);
        DebugMessage.Format("Current time: %d:%d", Hour, Minute);
        sc.AddMessageToLog(DebugMessage, 1);
        DebugMessage.Format("Updating stop loss to: %f on entry: %f", NewPossibleStop, orderPrice);
        sc.AddMessageToLog(DebugMessage, 1);
        DebugMessage.Format("Order price: %f", orderPrice);
        sc.AddMessageToLog(DebugMessage, 1);
        
        currentStop = currentLow - 2*sc.TickSize;
      }
    }
    
    // Flatten and cancel all if there is a current position but no stop
    if (PositionData.PositionQuantity == 1 && Stop1OrderID == 0)
    {
      sc.FlattenAndCancelAllOrders();
    }
    
    // Flatten and cancel all orders if it is right before the session ending.
    if (Hour == 14 && Minute ==59)
    {
      sc.FlattenAndCancelAllOrders();
    }
  }
  
}


Date Time Of Last Edit: 2019-09-17 17:46:57
[2019-09-16 23:52:26]
Chris_uk - Posts: 89
Hi Matt,

Are your chart session times for RTH only?

Chris
[2019-09-17 00:09:36]
Chris_uk - Posts: 89
Have you checked the trade activity log to look at the entry and exit times for these trades? They are probably being held overnight and over weekends.

Trade Activity Log

If this is the case, check out sc.FlattenAndCancelAllOrders();

Automated Trading From an Advanced Custom Study: sc.FlattenAndCancelAllOrders

and

ACSIL Programming Concepts: Performing Action When Certain Time is Encountered in Most Recent Chart Bar

This will allow you to flatten and cancel orders at a predetermined time to prevent this from happening.

It is also possible to do this with the Trade Window:

Basic Trading and the Trade Window: A >> Flatten And Cancel at Set Time

Cheers,
Chris
Date Time Of Last Edit: 2019-09-17 00:19:45
[2019-09-17 00:34:07]
MattK - Posts: 42
Hi Chris,

Thanks for the fast response time!

The chart sessions times settings are the default one: 8:30-15:14, I just now removed the evening and weekend sessions.

I tried the sc.FlattenAndCancelAllOrders() function instead of the CancelOrder() I was using and it seems to be working better.

However, when I backtest I still have issues with the trades: the system places around 3-4 trades per day and then one day it just stops for no apparent reason. The last trade was placed at 10:02 am and it didn't exit.

I looked at the chart settings, days to load, etc but don't understand why.

In the trade activity log, it says "Trade simulation fill" and then nothing. It doesn't say that the stop might have been cancelled or anything.

Thanks again for all your help.

Best,
Matt
[2019-09-17 10:54:58]
MattK - Posts: 42
I found a workaround.

I added this piece of code at the end:

if (PositionData.PositionQuantity == 1 && Stop1OrderID == 0)
    {
      sc.FlattenAndCancelAllOrders();
    }

Still working on why some stop loss are cancelled but at least now the system runs til' the end.
[2019-09-17 11:44:58]
User907968 - Posts: 802

// Data processing once the bar has closed.

if(sc.GetBarHasClosedStatus(Index-1)==BHCS_BAR_HAS_CLOSED)

I'm not sure this piece of code is doing what you intend, maybe that is contributing to the unexpected behaviour?

sc.GetBarHasClosedStatus()

The code block above will always evaluate to true, as by definition the bar prior to sc.Index is always closed.
[2019-09-17 13:20:06]
MattK - Posts: 42
Ooooh. That's true! I wanted to make it happen only once when the previous bar had closed.

Sorry if I ask stupid questions, I'm still new at this.

I tried adding this piece with the LastIndexProcessed to only run it once on this index:

// Keep the last processed index in a persistent variable
  int Index =sc.Index;
  int& LastIndexProcessed = sc.GetPersistentInt(3);
  
  // Data processing once the bar has closed.
  if(sc.GetBarHasClosedStatus(Index-1)==BHCS_BAR_HAS_CLOSED && Index > LastIndexProcessed)
{
// Update the last processed index so the data is not processed again on this bar
    LastIndexProcessed = Index;
}
  {

Thanks for all your help.
Best,
Matt
Date Time Of Last Edit: 2019-09-17 13:25:50
[2019-09-17 13:25:19]
User907968 - Posts: 802
Mostly I am using this when wanting to do something when bar closes (i.e. only once per bar) -

if(sc.GetBarHasClosedStatus() == BHCS_BAR_HAS_CLOSED)
{
// insert code here
}

No need for anything more complicated and no need to designate index if using auto-looping.

Also be aware of the following (although I've only found this to be relevant at the end of the session when there is no new bar until next day) -
ACSIL Programming Concepts: Determining if Last Chart Bar is Closed
Date Time Of Last Edit: 2019-09-17 13:36:34
[2019-09-17 13:45:50]
MattK - Posts: 42
Are you saying that this block of code executes only once and when the last bar closes (right before the new one starts?

Because the doc says: "The very last bar in the chart is never considered a closed bar until there is a new bar added to the chart."
sc.GetBarHasClosedStatus()

I'll try and see if that makes any difference, rather than using a complicated index variable and if statement.
[2019-09-17 14:00:42]
User907968 - Posts: 802
Are you saying that this block of code executes only once and when the last bar closes (right before the new one starts?

This is how I am using it, yes, either to limit complex operations to once per bar, or when adding drawings to the chart for example.
[2019-09-17 15:37:48]
MattK - Posts: 42
Thanks so much!! It works! You're a life saver.

Any chance I can bother you for one more thing?

My trailing stop loss is driving me crazy. I just want to trail the stop one tick below the low of the candles in an uptrend.
But after hours of testing, I still end up with massive stop losses.
If you have any ideas on this I'd love your opinion.

Best,
Matt
[2019-09-17 16:04:50]
MattK - Posts: 42
On my first backtest order, the Trade Activity says:

"Updating move to breakeven stop reference price on parent modification/fill to 2850". The entry is 2850 but it sets the stop at 2846! I don't understand why.

I changed the stop loss part to try and make it tighter but I think it makes it worst. I just can't figure out why...
It's driving me nuts.

// Update the trailing stop loss if the new low is higher than the previous low.
    // Gotta make sure the new stop loss is not lower than the initial one
    NewPossibleStop = currentLow - 2*sc.TickSize;
    
    if (PositionData.PositionQuantity == 1 && currentLow > previousLow && currentHigh > previousHigh && currentStop < NewPossibleStop && NewPossibleStop > orderStop)
    {
      s_SCTradeOrder ExistingOrder;
      if (sc.GetOrderByOrderID(Stop1OrderID, ExistingOrder) != SCTRADING_ORDER_ERROR)
      {
        
        s_SCNewOrder ModifyOrder;
        ModifyOrder.InternalOrderID = Stop1OrderID;
        ModifyOrder.Price1 = NewPossibleStop;

        sc.ModifyOrder(ModifyOrder);
        DebugMessage.Format("Current time: %d:%d", Hour, Minute);
        sc.AddMessageToLog(DebugMessage, 1);
        DebugMessage.Format("Updating stop loss to: %f on entry: %f", NewPossibleStop, orderPrice);
        sc.AddMessageToLog(DebugMessage, 1);
        
        currentStop = currentLow - 2*sc.TickSize;
      }
    }

[2019-09-17 17:30:39]
User907968 - Posts: 802
Sorry, I don't really have the time to dig into this too much and you know better than me what it is you are trying to actually achieve.

Did you try step by step debugging, so that you can better understand what your code is doing and when?
[2019-09-17 17:46:08]
MattK - Posts: 42
No problem, I understand perfectly.

I'm trying to debug by putting messages in the log with the value of different variables but the orderPrice is always returning 0.00000 even though I set it as a persistent variable:

DebugMessage.Format("Current time: %d:%d", Hour, Minute);
        sc.AddMessageToLog(DebugMessage, 1);
        DebugMessage.Format("Updating stop loss to: %f on entry: %f", NewPossibleStop, orderPrice);
        sc.AddMessageToLog(DebugMessage, 1);
        DebugMessage.Format("Order price: %f", orderPrice);
        sc.AddMessageToLog(DebugMessage, 1);

I'm working on it to understand why the variable is not returning anything.
I'll probably be able to find the issues in my code once I get the debugging part to work :)
[2019-09-17 18:04:58]
MattK - Posts: 42
Found the issue with my persistent variables!

I forgot the "&" in the declaration of the variables so it wasn't working.

Thanks again for all your help!

Have an amazing day!

Cheers,
Matt
[2019-09-17 18:19:18]
User907968 - Posts: 802
No problem, same to you.

I would definitely recommend trying de-bugging using visual studio, so much easier to get a handle on what's going on.

Step-By-Step ACSIL Debugging
Date Time Of Last Edit: 2019-09-17 18:23:31
[2019-09-17 18:29:18]
MattK - Posts: 42
Thanks for the advice, I will definitely download Visual Studio now.

Debugging with Notepad is not ideal :)

Best,
Matt

To post a message in this thread, you need to log in with your Sierra Chart account:

Login

Login Page - Create Account