Support Board
Date/Time: Tue, 01 Jul 2025 16:30:46 +0000
Post From: Trailing Stop for order entry that following the price.
[2025-05-14 03:37:25] |
Thanthou - Posts: 10 |
Hi John, I was curious if you could provide me with some SC best practices. I coded up a "Trailing Stop Limit" and have been curious about a few things for quite some time. 1. I would assume the trade cycle methodology is: 1. Place Trade 2. Confirm Fill 3. Exit Trade 4. Confirm Fill But, perhaps there is nuance and other things to factor in as well? 2. Best way to handle Entry/Exit Fills. What I have gets the job done, but I am certain it is not ideal. 3. Best way to handle sc.modify(order) -I was getting alot of error messages relating to the price and quantity being the same as before while attempting to modify it. However, the if statement was clearly blocking this from happening. I thought it was a floating point problem, but that wasn't it. Perhaps it was the speed (fast replay) that was the problem. Anyways, any insight would be appreciated. ----- MTSCALPER, here is a study that does what you are looking for. #include "sierrachart.h" SCDLLName("Custom Trailing Entry") bool short_logic(SCStudyInterfaceRef sc){ return sc.Close[sc.Index] > sc.Close[sc.Index - 1]; } bool long_logic(SCStudyInterfaceRef sc){ return sc.Close[sc.Index] < sc.Close[sc.Index - 1]; } bool check_entry_fill(SCStudyInterfaceRef sc, s_SCNewOrder order) { s_SCOrderFillData fill; int latest_index = sc.GetOrderFillArraySize() - 1; // Step 1: Check most recent fill if (latest_index >= 0 && sc.GetOrderFillEntry(latest_index, fill)) { if (fill.InternalOrderID == order.InternalOrderID) return true; } // Step 2: Iterate backwards if needed for (int i = latest_index - 1; i >= 0; --i) { if (sc.GetOrderFillEntry(i, fill) && fill.InternalOrderID == order.InternalOrderID) return true; } return false; } bool check_exit_fill(SCStudyInterfaceRef sc, s_SCNewOrder order) { s_SCOrderFillData fill; int latest_index = sc.GetOrderFillArraySize() - 1; // Step 1: Check most recent fill if (latest_index >= 0 && sc.GetOrderFillEntry(latest_index, fill)) { if (fill.InternalOrderID != order.InternalOrderID) return true; } return false; } void enter_trade(SCStudyInterfaceRef sc, float stop, float target, float offset, int direction, s_SCNewOrder& order, int& result) { order.OrderType = SCT_ORDERTYPE_TRAILING_STOP_LIMIT; order.OrderQuantity = 1; order.TimeInForce = SCT_TIF_GOOD_TILL_CANCELED; order.AttachedOrderStop1Type = SCT_ORDERTYPE_LIMIT; order.Stop1Offset = stop; order.AttachedOrderTarget1Type = SCT_ORDERTYPE_LIMIT; order.Target1Offset = target; if (direction == 1) { order.Price1 = sc.Close[sc.Index] + offset; result = sc.BuyEntry(order); } else if (direction == -1) { order.Price1 = sc.Close[sc.Index] - offset; result = sc.SellEntry(order); } } SCSFExport scsf_TrailingEntry(SCStudyInterfaceRef sc) { static bool waiting_to_enter = false; static bool in_trade = false; static int direction = 0.0f; static double trail_price = 0.0f; static int result = -1; static s_SCNewOrder entry_order; static double last_sent_trail_price = 0.0; static double offset = 5.0f; //Can also use sc.TickSize * ticks static double stop = 3.0f; static double target = 3.0f; if (sc.SetDefaults) { sc.GraphName = "Trailing Entry"; sc.AutoLoop = 1; sc.UpdateAlways = 1; sc.GraphRegion = 1; sc.ValueFormat = 3; sc.AllowOnlyOneTradePerBar = true; return; } if (sc.Index != sc.ArraySize - 1) return; if (!in_trade && !waiting_to_enter) { if (long_logic(sc)) direction = 1; else if (short_logic(sc)) direction = -1; if (direction != 0) { enter_trade(sc, stop, target, offset, direction, entry_order, result); if (result > 0) { waiting_to_enter = true; direction = direction; trail_price = sc.Close[sc.Index] + (direction * offset); SCString msg; msg.Format("Trailing %s: %.2f | Result: %d", (direction == 1 ? "Long" : "Short"), entry_order.Price1, result); sc.AddMessageToLog(msg, 1); } else { SCString msg; msg.Format("Order failed with code: %d", result); sc.AddMessageToLog(msg, 1); } } } if (waiting_to_enter && check_entry_fill(sc, entry_order)) { waiting_to_enter = false; in_trade = true; } if (in_trade && check_exit_fill(sc,entry_order)) { in_trade = false; trail_price = 0.0f; direction = 0; } if (waiting_to_enter && (direction == 1 || direction == -1)) { double candidate_price = sc.RoundToTickSize(sc.Close[sc.Index] + (direction * offset), sc.TickSize); bool should_trail = (direction == 1 && candidate_price < trail_price) || (direction == -1 && candidate_price > trail_price); if (should_trail && candidate_price != last_sent_trail_price) { s_SCTradeOrder status; if (!sc.GetOrderByOrderID(entry_order.InternalOrderID, status) || status.OrderStatusCode == SCT_OSC_OPEN) return; trail_price = candidate_price; last_sent_trail_price = candidate_price; s_SCNewOrder modify; modify.InternalOrderID = entry_order.InternalOrderID; modify.Price1 = trail_price; int result = sc.ModifyOrder(modify); if (result <= 0) { SCString msg; msg.Format("%s Modify failed: %d %.2f", (direction == 1 ? "Long" : "Short"), result, trail_price); sc.AddMessageToLog(msg, 1); } } } } |