Support Board
Date/Time: Sun, 02 Feb 2025 14:45:06 +0000
[Programming Help] - ACSIL - trailing stop loss and orders
View Count: 3409
[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: 825 |
// 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: 825 |
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: 825 |
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: 825 |
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: 825 |
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: