Login Page - Create Account

Support Board


Date/Time: Sat, 23 Nov 2024 08:44:57 +0000



[Programming Help] - Move stop to breakeven: auto trading system

View Count: 259

[2024-08-26 13:27:11]
User357489 - Posts: 72
Hi SC Engineering/wider community, here's my auto trading system in its current iteration.

As im watching it open and close trades, i notice that it isnt actually modifying the stop and moving it to breakeven as per my attempts here:
#include "sierrachart.h"
#include <unordered_map>
#include <chrono>

SCDLLName("Sweep Genie")

struct TradeInfo
{
std::chrono::time_point<std::chrono::steady_clock> startTime;
double startPrice;
bool tradeEntered;
int positionType; // 1 for buy, -1 for sell
bool movedToBreakeven; // To track if stop has been moved to breakeven
int stopOrderID; // To store the stop order ID
};

std::unordered_map<int, TradeInfo> g_TradeInfo;

SCSFExport scsf_SweepGenie(SCStudyInterfaceRef sc)
{
if (sc.SetDefaults)
{
sc.GraphName = "Sweep Genie";
sc.GraphRegion = 0;
sc.AutoLoop = 0; // Manual looping

// Grouping all Study ID inputs together
sc.Input[10].Name = "VWAP Study ID";
sc.Input[10].SetInt(2); // VWAP Study ID

sc.Input[11].Name = "Previous Day OHLC Study ID";
sc.Input[11].SetInt(3); // Previous Day OHLC Study ID

sc.Input[12].Name = "Current Day OHLC Study ID";
sc.Input[12].SetInt(6); // Current Day OHLC Study ID

// Other inputs
sc.Input[1].Name = "Bid Highlight Color";
sc.Input[1].SetColor(RGB(0, 255, 0)); // Default bid highlight color

sc.Input[2].Name = "Ask Highlight Color";
sc.Input[2].SetColor(RGB(255, 0, 0)); // Default ask highlight color

sc.Input[3].Name = "Number of Levels to Check";
sc.Input[3].SetInt(5); // Number of bid/ask levels to check

sc.Input[4].Name = "Order Quantity";
sc.Input[4].SetInt(1); // Default order quantity

sc.Input[5].Name = "Enable Order Placement";
sc.Input[5].SetYesNo(0); // Default to not placing orders

sc.Input[6].Name = "Stop Loss (Ticks)";
sc.Input[6].SetInt(10); // Stop loss in ticks from entry price

sc.Input[7].Name = "Take Profit (Ticks)";
sc.Input[7].SetInt(20); // Take profit in ticks from entry price

sc.Input[8].Name = "Trailing Stop Trigger (Ticks)";
sc.Input[8].SetInt(15); // Trailing Stop Trigger after specified ticks in profit

sc.Input[9].Name = "Trailing Stop Offset (Ticks)";
sc.Input[9].SetInt(5); // Trailing Stop Offset from current price

sc.Input[13].Name = "Recent Volume Threshold";
sc.Input[13].SetInt(100); // Default threshold value for recent volume

sc.UsesMarketDepthData = 1; // Enable market depth data

return;
}

int vwapStudyID = sc.Input[10].GetInt();
int prevOHLCStudyID = sc.Input[11].GetInt();
int currentOHLCStudyID = sc.Input[12].GetInt();

COLORREF bidHighlightColor = sc.Input[1].GetColor();
COLORREF askHighlightColor = sc.Input[2].GetColor();
int numLevelsToCheck = sc.Input[3].GetInt();
int orderQuantity = sc.Input[4].GetInt();
bool enableOrderPlacement = sc.Input[5].GetYesNo();
int stopLossTicks = sc.Input[6].GetInt();
int takeProfitTicks = sc.Input[7].GetInt();
int trailingStopTriggerTicks = sc.Input[8].GetInt();
int trailingStopOffsetTicks = sc.Input[9].GetInt();
int volumeThreshold = sc.Input[13].GetInt();

auto& tradeInfo = g_TradeInfo[sc.ChartNumber];

s_MarketDepthEntry depthEntry;
const int maxOrders = 100;
n_ACSIL::s_MarketOrderData bidOrders[maxOrders];
n_ACSIL::s_MarketOrderData askOrders[maxOrders];

sc.DeleteACSChartDrawing(sc.ChartNumber, TOOL_DELETE_ALL, 0);

int bidMBOSum = 0;
int askMBOSum = 0;
int bidMarketDepthQuantitySum = 0;
int askMarketDepthQuantitySum = 0;

// Declare and initialize arrays for current and previous VWAP and OHLC levels
SCFloatArray vwapArray, prevVwapArray, ohlcArray, prevOhlcArray;

// Get arrays from the respective studies
sc.GetStudyArrayUsingID(vwapStudyID, 1, vwapArray); // VWAP
sc.GetStudyArrayUsingID(vwapStudyID, 10, prevVwapArray); // Previous VWAP
sc.GetStudyArrayUsingID(currentOHLCStudyID, 1, ohlcArray); // Current OHLC
sc.GetStudyArrayUsingID(prevOHLCStudyID, 1, prevOhlcArray); // Previous OHLC

// Get the current prices for each level
double currentVWAP = vwapArray[sc.ArraySize - 1];
double currentVWAP_0_5_Up = vwapArray[2];
double currentVWAP_0_5_Down = vwapArray[3];
double currentVWAP_1_0_Up = vwapArray[4];
double currentVWAP_1_0_Down = vwapArray[5];
double currentVWAP_1_5_Up = vwapArray[6];
double currentVWAP_1_5_Down = vwapArray[7];
double currentVWAP_2_0_Up = vwapArray[8];
double currentVWAP_2_0_Down = vwapArray[9];

double previousVWAP = prevVwapArray[sc.ArraySize - 1];
double previousVWAP_0_5_Up = prevVwapArray[11];
double previousVWAP_0_5_Down = prevVwapArray[12];
double previousVWAP_1_0_Up = prevVwapArray[13];
double previousVWAP_1_0_Down = prevVwapArray[14];
double previousVWAP_1_5_Up = prevVwapArray[15];
double previousVWAP_1_5_Down = prevVwapArray[16];
double previousVWAP_2_0_Up = prevVwapArray[17];
double previousVWAP_2_0_Down = prevVwapArray[18];

double currentOpen = ohlcArray[1];
double currentHigh = ohlcArray[2];
double currentLow = ohlcArray[3];
double currentHalfback = ohlcArray[9];

double previousOpen = prevOhlcArray[1];
double previousHigh = prevOhlcArray[2];
double previousLow = prevOhlcArray[3];
double previousClose = prevOhlcArray[4];
double previousHalfback = prevOhlcArray[9];

// Declare variables for scenarios
bool scenarioForBuyMet = false;
bool scenarioForSellMet = false;
double Pref = (sc.Bid + sc.Ask) / 2; // Calculate Pref (mid-price or some representation of current price)
SCString buyScenarioMessage;
SCString sellScenarioMessage;

// Buy scenarios within +/- 5 ticks of each level
if ((Pref >= previousHigh - 5 * sc.TickSize && Pref <= previousHigh + 5 * sc.TickSize) ||
(Pref >= previousLow - 5 * sc.TickSize && Pref <= previousLow + 5 * sc.TickSize) ||
(Pref >= previousOpen - 5 * sc.TickSize && Pref <= previousOpen + 5 * sc.TickSize) ||
(Pref >= previousClose - 5 * sc.TickSize && Pref <= previousClose + 5 * sc.TickSize) ||
(Pref >= previousHalfback - 5 * sc.TickSize && Pref <= previousHalfback + 5 * sc.TickSize) ||
(Pref >= currentHigh - 5 * sc.TickSize && Pref <= currentHigh + 5 * sc.TickSize) ||
(Pref >= currentLow - 5 * sc.TickSize && Pref <= currentLow + 5 * sc.TickSize) ||
(Pref >= currentOpen - 5 * sc.TickSize && Pref <= currentOpen + 5 * sc.TickSize) ||
(Pref >= currentHalfback - 5 * sc.TickSize && Pref <= currentHalfback + 5 * sc.TickSize) ||
(Pref >= currentVWAP - 5 * sc.TickSize && Pref <= currentVWAP + 5 * sc.TickSize) ||
(Pref >= currentVWAP_0_5_Up - 5 * sc.TickSize && Pref <= currentVWAP_0_5_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_0_5_Down - 5 * sc.TickSize && Pref <= currentVWAP_0_5_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_0_Up - 5 * sc.TickSize && Pref <= currentVWAP_1_0_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_0_Down - 5 * sc.TickSize && Pref <= currentVWAP_1_0_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_5_Up - 5 * sc.TickSize && Pref <= currentVWAP_1_5_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_5_Down - 5 * sc.TickSize && Pref <= currentVWAP_1_5_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_2_0_Up - 5 * sc.TickSize && Pref <= currentVWAP_2_0_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_2_0_Down - 5 * sc.TickSize && Pref <= currentVWAP_2_0_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP - 5 * sc.TickSize && Pref <= previousVWAP + 5 * sc.TickSize) ||
(Pref >= previousVWAP_0_5_Up - 5 * sc.TickSize && Pref <= previousVWAP_0_5_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_0_5_Down - 5 * sc.TickSize && Pref <= previousVWAP_0_5_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_0_Up - 5 * sc.TickSize && Pref <= previousVWAP_1_0_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_0_Down - 5 * sc.TickSize && Pref <= previousVWAP_1_0_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_5_Up - 5 * sc.TickSize && Pref <= previousVWAP_1_5_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_5_Down - 5 * sc.TickSize && Pref <= previousVWAP_1_5_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_2_0_Up - 5 * sc.TickSize && Pref <= previousVWAP_2_0_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_2_0_Down - 5 * sc.TickSize && Pref <= previousVWAP_2_0_Down + 5 * sc.TickSize))
{
scenarioForBuyMet = true;
buyScenarioMessage = "Scenario: Buy near important levels";
}

// Sell scenarios within +/- 5 ticks of each level
if ((Pref >= previousHigh - 5 * sc.TickSize && Pref <= previousHigh + 5 * sc.TickSize) ||
(Pref >= previousLow - 5 * sc.TickSize && Pref <= previousLow + 5 * sc.TickSize) ||
(Pref >= previousOpen - 5 * sc.TickSize && Pref <= previousOpen + 5 * sc.TickSize) ||
(Pref >= previousClose - 5 * sc.TickSize && Pref <= previousClose + 5 * sc.TickSize) ||
(Pref >= previousHalfback - 5 * sc.TickSize && Pref <= previousHalfback + 5 * sc.TickSize) ||
(Pref >= currentHigh - 5 * sc.TickSize && Pref <= currentHigh + 5 * sc.TickSize) ||
(Pref >= currentLow - 5 * sc.TickSize && Pref <= currentLow + 5 * sc.TickSize) ||
(Pref >= currentOpen - 5 * sc.TickSize && Pref <= currentOpen + 5 * sc.TickSize) ||
(Pref >= currentHalfback - 5 * sc.TickSize && Pref <= currentHalfback + 5 * sc.TickSize) ||
(Pref >= currentVWAP - 5 * sc.TickSize && Pref <= currentVWAP + 5 * sc.TickSize) ||
(Pref >= currentVWAP_0_5_Up - 5 * sc.TickSize && Pref <= currentVWAP_0_5_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_0_5_Down - 5 * sc.TickSize && Pref <= currentVWAP_0_5_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_0_Up - 5 * sc.TickSize && Pref <= currentVWAP_1_0_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_0_Down - 5 * sc.TickSize && Pref <= currentVWAP_1_0_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_5_Up - 5 * sc.TickSize && Pref <= currentVWAP_1_5_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_1_5_Down - 5 * sc.TickSize && Pref <= currentVWAP_1_5_Down + 5 * sc.TickSize) ||
(Pref >= currentVWAP_2_0_Up - 5 * sc.TickSize && Pref <= currentVWAP_2_0_Up + 5 * sc.TickSize) ||
(Pref >= currentVWAP_2_0_Down - 5 * sc.TickSize && Pref <= currentVWAP_2_0_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP - 5 * sc.TickSize && Pref <= previousVWAP + 5 * sc.TickSize) ||
(Pref >= previousVWAP_0_5_Up - 5 * sc.TickSize && Pref <= previousVWAP_0_5_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_0_5_Down - 5 * sc.TickSize && Pref <= previousVWAP_0_5_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_0_Up - 5 * sc.TickSize && Pref <= previousVWAP_1_0_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_0_Down - 5 * sc.TickSize && Pref <= previousVWAP_1_0_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_5_Up - 5 * sc.TickSize && Pref <= previousVWAP_1_5_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_1_5_Down - 5 * sc.TickSize && Pref <= previousVWAP_1_5_Down + 5 * sc.TickSize) ||
(Pref >= previousVWAP_2_0_Up - 5 * sc.TickSize && Pref <= previousVWAP_2_0_Up + 5 * sc.TickSize) ||
(Pref >= previousVWAP_2_0_Down - 5 * sc.TickSize && Pref <= previousVWAP_2_0_Down + 5 * sc.TickSize))
{
scenarioForSellMet = true;
sellScenarioMessage = "Scenario: Sell near important levels";
}

// Market Depth and Recent Volume Check after scenario check
if (scenarioForBuyMet)
{
// Calculate market depth conditions
for (int level = 0; level < numLevelsToCheck; ++level)
{
if (sc.GetBidMarketDepthEntryAtLevel(depthEntry, level))
{
bidMBOSum += depthEntry.NumOrders;
bidMarketDepthQuantitySum += depthEntry.Quantity;
}
}

bool marketDepthForBuy = (bidMBOSum > askMBOSum) &&
(bidMarketDepthQuantitySum > askMarketDepthQuantitySum) &&
(sc.GetRecentAskVolumeAtPrice(Pref) >= volumeThreshold);

// Log relevant details
SCString message;
message.Format("Buy Scenario Met: %s, BidMBOSum: %d, AskMBOSum: %d, BidMarketDepthQuantitySum: %d, AskMarketDepthQuantitySum: %d, Recent Ask Volume: %u, Recent Bid Volume: %u, Pref: %f",
buyScenarioMessage.GetChars(), bidMBOSum, askMBOSum,
bidMarketDepthQuantitySum, askMarketDepthQuantitySum,
sc.GetRecentAskVolumeAtPrice(Pref), sc.GetRecentBidVolumeAtPrice(Pref), Pref);
sc.AddMessageToLog(message, 0);

if (marketDepthForBuy)
{
// Place a buy order
s_UseTool tool;
tool.Clear();
tool.ChartNumber = sc.ChartNumber;
tool.DrawingType = DRAWING_RECTANGLEHIGHLIGHT;
tool.AddMethod = UTAM_ADD_OR_ADJUST;
tool.BeginValue = sc.Bid - numLevelsToCheck * sc.TickSize;
tool.EndValue = sc.Bid + sc.TickSize / 2;
tool.BeginDateTime = sc.BaseDateTimeIn[sc.ArraySize - 1];
tool.EndDateTime = sc.BaseDateTimeIn[0];
tool.Color = bidHighlightColor;
tool.SecondaryColor = bidHighlightColor;
tool.TransparencyLevel = 75;
tool.LineWidth = 1;
sc.UseTool(tool);

if (enableOrderPlacement)
{
s_SCNewOrder newOrder;
newOrder.OrderQuantity = orderQuantity;
newOrder.OrderType = SCT_ORDERTYPE_MARKET;
newOrder.Stop1Offset = stopLossTicks * sc.TickSize;
newOrder.Target1Offset = takeProfitTicks * sc.TickSize;
newOrder.AttachedOrderTarget1Type = SCT_ORDERTYPE_LIMIT;
newOrder.AttachedOrderStopAllType = SCT_ORDERTYPE_STOP;

int InternalOrderID = sc.BuyOrder(newOrder);

if (InternalOrderID != 0)
{
tradeInfo.startTime = std::chrono::steady_clock::now();
tradeInfo.startPrice = sc.Bid;
tradeInfo.tradeEntered = true;
tradeInfo.positionType = 1;
tradeInfo.movedToBreakeven = false;
tradeInfo.stopOrderID = newOrder.Stop1InternalOrderID;
SCString message;
message.Format("Buy order placed at price: %f", tradeInfo.startPrice);
sc.AddMessageToLog(message, 0);
}
}
}
}
else if (scenarioForSellMet)
{
// Calculate market depth conditions
for (int level = 0; level < numLevelsToCheck; ++level)
{
if (sc.GetAskMarketDepthEntryAtLevel(depthEntry, level))
{
askMBOSum += depthEntry.NumOrders;
askMarketDepthQuantitySum += depthEntry.Quantity;
}
}

bool marketDepthForSell = (askMBOSum > bidMBOSum) &&
(askMarketDepthQuantitySum > bidMarketDepthQuantitySum) &&
(sc.GetRecentBidVolumeAtPrice(Pref) >= volumeThreshold);

// Log relevant details
SCString message;
message.Format("Sell Scenario Met: %s, BidMBOSum: %d, AskMBOSum: %d, BidMarketDepthQuantitySum: %d, AskMarketDepthQuantitySum: %d, Recent Ask Volume: %u, Recent Bid Volume: %u, Pref: %f",
sellScenarioMessage.GetChars(), bidMBOSum, askMBOSum,
bidMarketDepthQuantitySum, askMarketDepthQuantitySum,
sc.GetRecentAskVolumeAtPrice(Pref), sc.GetRecentBidVolumeAtPrice(Pref), Pref);
sc.AddMessageToLog(message, 0);

if (marketDepthForSell)
{
// Place a sell order
s_UseTool tool;
tool.Clear();
tool.ChartNumber = sc.ChartNumber;
tool.DrawingType = DRAWING_RECTANGLEHIGHLIGHT;
tool.AddMethod = UTAM_ADD_OR_ADJUST;
tool.BeginValue = sc.Ask - sc.TickSize / 2;
tool.EndValue = sc.Ask + numLevelsToCheck * sc.TickSize;
tool.BeginDateTime = sc.BaseDateTimeIn[sc.ArraySize - 1];
tool.EndDateTime = sc.BaseDateTimeIn[0];
tool.Color = askHighlightColor;
tool.SecondaryColor = askHighlightColor;
tool.TransparencyLevel = 75;
tool.LineWidth = 1;
sc.UseTool(tool);

if (enableOrderPlacement)
{
s_SCNewOrder newOrder;
newOrder.OrderQuantity = orderQuantity;
newOrder.OrderType = SCT_ORDERTYPE_MARKET;
newOrder.Stop1Offset = stopLossTicks * sc.TickSize;
newOrder.Target1Offset = takeProfitTicks * sc.TickSize;
newOrder.AttachedOrderTarget1Type = SCT_ORDERTYPE_LIMIT;
newOrder.AttachedOrderStopAllType = SCT_ORDERTYPE_STOP;

int InternalOrderID = sc.SellOrder(newOrder);

if (InternalOrderID != 0)
{
tradeInfo.startTime = std::chrono::steady_clock::now();
tradeInfo.startPrice = sc.Ask;
tradeInfo.tradeEntered = true;
tradeInfo.positionType = -1;
tradeInfo.movedToBreakeven = false;
tradeInfo.stopOrderID = newOrder.Stop1InternalOrderID;
SCString message;
message.Format("Sell order placed at price: %f", tradeInfo.startPrice);
sc.AddMessageToLog(message, 0);
}
}
}
}

// Monitoring and managing the trade (both buy and sell)
if (tradeInfo.tradeEntered)
{
double currentPrice = sc.Close[sc.ArraySize - 1];
int profitTicks = static_cast<int>((currentPrice - tradeInfo.startPrice) / sc.TickSize) * tradeInfo.positionType;

// Move stop to breakeven logic
if (!tradeInfo.movedToBreakeven && profitTicks >= trailingStopTriggerTicks)
{
s_SCNewOrder modifyOrder;
modifyOrder.InternalOrderID = tradeInfo.stopOrderID;
modifyOrder.Price1 = tradeInfo.startPrice + trailingStopOffsetTicks * sc.TickSize * tradeInfo.positionType; // Adjust stop to breakeven

int modifyResult = sc.ModifyOrder(modifyOrder);
if (modifyResult == 1)
{
SCString message;
message.Format("Stop moved to breakeven at price: %f", tradeInfo.startPrice);
sc.AddMessageToLog(message, 0);
tradeInfo.movedToBreakeven = true;
}
else
{
SCString message;
message.Format("Failed to modify stop order for breakeven. Error: %d", modifyResult);
sc.AddMessageToLog(message, 1);
}
}
}
}
not overly sure where i may have gone wrong

Ty in advance :Dc
[2024-08-30 16:39:03]
OctoPi - Posts: 37
Looks like your BE logic would never trigger if trade position is short.

What's the problem exactly?
[2024-08-31 09:17:22]
User357489 - Posts: 72
Hi OctoPi

So as I've been watching, it opens trades and on the occasions where the trade has gone X amount of ticks into profit, I was expecting the stop order to move to break even. I noticed it worked like maybe once but sporadically.

It's not consistent and doesn't trigger the move to break even immediately upon reaching X ticks.

Thanks 🙏🏽
[2024-08-31 09:56:27]
User431178 - Posts: 540
@User357489

Before delving into your code, I have a question.
Is there a reason why you would not use the built in move to breakeven and trailing stop functionality?
[2024-08-31 10:01:55]
User357489 - Posts: 72
I'm a novice, so may have overlooked it.

Started to get overwhelmed so reached out for guidance
[2024-08-31 10:25:22]
User431178 - Posts: 540
Ok, understood, didn't want to suggest it if you had already discounted it for some reason.
There are some examples in SierraChart\ACS_Source\TradingSystem.cpp, I think you'll find it much easier.
[2024-08-31 11:01:40]
User357489 - Posts: 72
That's alright, appreciate it!

Ty I'll try that and go from there 😁
[2024-09-01 16:31:09]
OctoPi - Posts: 37
My only point was - your code takes a negative value if the trade is short, which you then compare to positive value setting. Wouldn't work unless you take absolute value.

In either scenario, the suggestion above is a better one - use SC built-in stuff.

If you want something pre-built that doesn't require coding - give our plugin a whirl.

Click/No Code Strategy Builder, Algo Portfolio Composer plugin for Sierra Chart

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

Login

Login Page - Create Account