OpenAlgo – An Open-Source Algo Trading Platform for Amibroker Users

Hi everyone,

I’ve been using Amibroker for over a decade, and like many here, I have learned a lot from Dr. Tomasz Janeczko and the countless contributors to the Amibroker AFL ecosystem. Their work has shaped the way we develop trading strategies and automate execution. Inspired by this, I wanted to create something that makes automated trading simple, efficient, and self-hosted.

That’s how OpenAlgo was born – an Open-Source platform designed to make trading automation easy while giving traders full control over the codes and infrastructure.

What is OpenAlgo?

  • Self-hosted and fully Open-Source, giving traders complete ownership.
  • Runs on your desktop, laptop, or cloud servers (Windows, Mac, Linux, VPS).
  • Built using Python Flask, with a clean, user-friendly UI powered by DaisyUI/Tailwind CSS.
  • Uses SQLite for smooth and reliable local data management.
  • Supports 12+ Indian brokers like Zerodha, Upstox, Angel, Dhan, Fyers, Kotak, and more.
  • Easily connects with Amibroker for fast and reliable order execution.
  • Supports Amiquotes to fetch intraday and EOD data directly from the Brokers Feed (Supports NSE, BSE, MCX , NFO, CDS , BFO exchanges)
  • Supports Both VBscript method and Modern Amibroker Internet Functions to Place Orders

Minimum requirements

AmiBroker version 6.00 or higher (Supports 32-bit and 64-bit Amibroker). Recommended to use 6.4 or Higher versions.

OpenAlgo is Open-Source and completely self-hosted, so there’s no dependency on third-party services. If you’re interested, you can check out the source code here:

:link: GitHub: GitHub - marketcalls/openalgo: Open Source Algo Trading Platform for Everyone

Requirements for Installing OpenAlgo to Automate Amibroker Strategies

  • Windows 10 or Higher

  • Visual Studio Code (VS Code) is installed.

  • Python version 3.10 or higher version installed

  • Git for cloning the repository

OpenAlgo is built with Amibroker users in mind, aiming to make algorithmic trading more accessible, flexible, and efficient. I would love to hear your thoughts, feedback, and suggestions on how OpenAlgo can further enhance the trading experience for Amibroker users.

Looking forward to your thoughts!

4 Likes

Hi Rajandran,

Congratulations.

The Broker specs have been so unstandardised that many have wished for such a middleware.
I hope it helps many who have not been able to build their own interfaces.

I have been on a similar journey and would like to highlight the AmiBroker interfaces,
used by you.

AmiBroker being the pinnacle of Trading softwares,your design choice for universally
using http calls for data and trading interface could in the future be pursued to
follow the AB design philosophy.

Using COM/OLE to import data via AmiQuote will work, but it has too many limitations.
Especially for intraday live data.

Similarly, I can see that the trading interface is wholly VBScript inside AFL. So much COM
code inside AFL is detrimental to performance. GetTradingInterface() is for this keeping AFL as light as possible as opposed to embedding VBScript inside.
A lot of COM stuff is possibly limited by critical section there by serializing calls and processing all http in the afl threads, on the other hand, the trading interface transfers the overhead to a separate compiled appl.

Hopefully, future enhancements align with AB design philosophy.
I would like to understand why the choice of http calls over the websocket implementations provided by all brokers.

1 Like

I couldn't agree more. The design of trading interface should follow exactly the design of open source IBController

A separate application that does all the bookkeeping, allowing AFL to be lightweight.

It is worth noting that VBScript is obsolete and should not be used in new projects.

1 Like

Thank you for the insights, Tomasz. I completely agree that VBScript is obsolete, and I am already replacing the VBScript-based implementation with native AmiBroker Internet functions to send POST requests for order placement. However, to maintain backward compatibility for users on older versions, the VBScript option will still be available.

I also appreciate the reference to IBController and will explore its design further to see how OpenAlgo can align with best practices while ensuring flexibility for users.

I completely agree that broker specifications are highly unstandardized, making it difficult for traders to build their own interfaces. OpenAlgo aims to bridge this gap by providing a middleware that simplifies the integration process for AmiBroker users.

Regarding the trading interface, I acknowledge the performance concerns related to embedding VBScript inside AFL. That's why I am in the process of replacing the VBScript implementation with AmiBroker’s native Internet functions for sending POST requests, while still keeping VBScript as a fallback for older versions. I will also explore IBController further to see how OpenAlgo can align better with AmiBroker’s design philosophy.

As for the choice of HTTP over WebSockets, the primary reason is to ensure compatibility across a wide range of Indian brokers, many of whom still rely on REST APIs for order placement

1 Like

This is my endeavour, WS based data plugin.

WsRtd - Websocket-Json standard Data plugin - Test Release - Plug-ins - AmiBroker Community Forum

Working on TI here,
BrokerWS is a Trading Interface for AmiBroker using Json & WebSocket

Data plugin is 100% compliant and very high performance unlike some 3rd party stuff.

2 Likes

thats cool will experiment on that. I truly appreciate the effort you've put into bringing broker WebSocket data into AmiBroker. it's no small feat, given the lack of standardization across different brokers.

Here’s the architecture I’ve designed to handle signals (BUY, SELL, SHORT, COVER) from AmiBroker. The flow is straightforward: AmiBroker sends a simple POST request to OpenAlgo, which then translates it into the broker’s specific API format.

The goal is to keep the integration lightweight on the AmiBroker side while centralizing the heavy lifting in OpenAlgo. This way, the architecture remains scalable and adaptable to various brokers and exchanges.

Would love to hear your thoughts or any suggestions for further improvements!

2 Likes

@Tomasz and @nsm51

Currently I use OpenAlgo (Authentication, Symbol Management, Common Broker API Management, GUI based Trade Reports, API Validation (testing strategies) and Trade Management (Time Based Trade Management) etc.

This is the Amibroker Native Function to place orders. It is native to Amibroker to placeorder. Will it be efficient enough or still IBcontroller techniques are preferred?

// OpenAlgo - Amibroker SmartOrder Chart Trading Module v2.0
// Date - 12/12/2024

_SECTION_BEGIN("OpenAlgo SmartOrder Trading Module - Modern Methods");

RequestTimedRefresh(1, False);

// Define parameter controls
apikey = ParamStr("OpenAlgo API Key", "******");
strategy = ParamStr("Strategy", "Amibroker");
symbol = ParamStr("Symbol", "YESBANK");
exchange = ParamList("Exchange", "NSE|NFO|BSE|MCX|CDS");
pricetype = ParamStr("Price Type", "MARKET");
product = ParamList("Product", "MIS|NRML|CNC");
quantity = Param("Quantity", 1, 1, 1000, 1);
position_size = Param("Position Size", 0, -1000, 1000, 1);

host = ParamStr("Host", "http://127.0.0.1:5000");
ver = ParamStr("API Version", "v1");

VoiceAlert = ParamList("Voice Alert", "Disable|Enable", 1);
EnableAlgo = ParamList("Algo Mode", "Disable|Enable", 0);
EnableButton = ParamList("Button Trading", "Disable|Enable", 0);

Entrydelay = Param("Entry Delay",0,0,1,1);
Exitdelay = Param("Exit Delay",0,0,1,1);

AlgoBuy = lastvalue(Ref(Buy,-Entrydelay));
AlgoSell = lastvalue(Ref(Sell,-Exitdelay));
AlgoShort = lastvalue(Ref(Short,-Entrydelay));
AlgoCover = lastvalue(Ref(Cover,-Exitdelay));

// Construct URL base
bridgeurl = host + "/api/" + ver;

static_name_ = Name()+GetChartID()+interval(2)+strategy;
static_name_algo = static_name_+interval(2)+strategy+"algostatus";



//OpenAlgo Dashboard

GfxSelectFont( "BOOK ANTIQUA", 14, 100 );
GfxSetBkMode( 1 );
if(EnableAlgo == "Enable")
{
AlgoStatus = "Algo Enabled";
GfxSetTextColor( colorGreen ); 
GfxTextOut( "Algostatus : "+AlgoStatus , 20, 40); 
if(Nz(StaticVarGet(static_name_algo),0)!=1)
{
_TRACE("Algo Status : Enabled");
StaticVarSet(static_name_algo, 1);
}
}
if(EnableAlgo == "Disable")
{
AlgoStatus = "Algo Disabled";
GfxSetTextColor( colorRed ); 
GfxTextOut( "Algostatus : "+AlgoStatus , 20, 40); 
if(Nz(StaticVarGet(static_name_algo),0)!=0)
{
_TRACE("Algo Status : Disabled");
StaticVarSet(static_name_algo, 0);
}
}



// Function to Place Smart Order
function PlaceSmartOrder(action, quantity, position_size) {
    postData = "{\"apikey\": \"" + apikey + "\", " +
               "\"strategy\": \"" + strategy + "\", " +
               "\"symbol\": \"" + symbol + "\", " +
               "\"action\": \"" + action + "\", " +
               "\"exchange\": \"" + exchange + "\", " +
               "\"pricetype\": \"" + pricetype + "\", " +
               "\"product\": \"" + product + "\", " +
               "\"quantity\": \"" + quantity + "\", " +
               "\"position_size\": \"" + position_size + "\"}";

    headers = "Content-Type: application/json\r\n" +
              "Accept-Encoding: gzip, deflate\r\n";
    InternetSetHeaders(headers);

    _TRACE("Smart Order Request Sent: " + postData); // Log request
    ih = InternetPostRequest(bridgeurl + "/placesmartorder", postData);

    if (ih) {
        response = "";
        while ((line = InternetReadString(ih)) != "") {
            response += line;
        }
        _TRACEF("Smart Order Response: %s", response);
        if (VoiceAlert == "Enable") Say(action + " Smart Order Placed.");
        InternetClose(ih);
    } else {
        _TRACE("Failed to place smart order.");
    }
}

// Function to Exit Order
function ExitOrder(action) {
    postData = "{\"apikey\": \"" + apikey + "\", " +
               "\"strategy\": \"" + strategy + "\", " +
               "\"symbol\": \"" + symbol + "\", " +
               "\"action\": \"" + action + "\", " +
               "\"exchange\": \"" + exchange + "\", " +
               "\"pricetype\": \"" + pricetype + "\", " +
               "\"product\": \"" + product + "\", " +
               "\"quantity\": \"0\", " +
               "\"position_size\": \"0\"}";

    headers = "Content-Type: application/json\r\n" +
              "Accept-Encoding: gzip, deflate\r\n";
    InternetSetHeaders(headers);

    _TRACE("Exit Order Request Sent: " + postData); // Log request
    ih = InternetPostRequest(bridgeurl + "/placesmartorder", postData);

    if (ih) {
        response = "";
        while ((line = InternetReadString(ih)) != "") {
            response += line;
        }
        _TRACEF("Exit Order Response: %s", response);
        if (VoiceAlert == "Enable") Say(action + " Exit Order Placed.");
        InternetClose(ih);
    } else {
        _TRACE("Failed to place exit order.");
    }
}

// Function to Square Off All Positions
function SquareOffAll() {
    postData = "{\"apikey\": \"" + apikey + "\", " +
               "\"strategy\": \"" + strategy + "\"}";

    headers = "Content-Type: application/json\r\n" +
              "Accept-Encoding: gzip, deflate\r\n";
    InternetSetHeaders(headers);

    _TRACE("Square Off Request Sent: " + postData); // Log request
    ih = InternetPostRequest(bridgeurl + "/closeposition", postData);

    if (ih) {
        response = "";
        while ((line = InternetReadString(ih)) != "") {
            response += line;
        }
        _TRACEF("Square Off Response: %s", response);
        if (VoiceAlert == "Enable") Say("All positions squared off.");
        InternetClose(ih);
    } else {
        _TRACE("Failed to square off positions.");
    }
}

// Function to Draw Buttons
function DrawButton(Text, x1, y1, x2, y2, colorFrom, colorTo) {
    GfxSetOverlayMode(0);
    GfxSelectFont("Verdana", 9, 700);
    GfxSetBkMode(1);
    GfxGradientRect(x1, y1, x2, y2, colorFrom, colorTo);
    GfxDrawText(Text, x1, y1, x2, y2, 32 | 1 | 4 | 16);
}

// Execution Module
if (EnableAlgo != "Disable") {
    lasttime = StrFormat("%0.f", LastValue(BarIndex()));
    SetChartBkColor(colorDarkGrey);

    if (EnableAlgo == "Enable") {
        if (AlgoBuy == True AND AlgoCover == True AND StaticVarGet(static_name_ + "buyCoverAlgo") == 0 AND StaticVarGetText(static_name_ + "buyCoverAlgo_barvalue") != lasttime) {
            PlaceSmartOrder("BUY", quantity, quantity);
            if (VoiceAlert == "Enable") Say("Buy Order Triggered");
            _TRACE("API Request: " + sm_api_request);
            _TRACE("API Response: " + sm_api_response);
            StaticVarSetText(static_name_ + "buyCoverAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "buyCoverAlgo", 1);
        } else if (AlgoBuy != True OR AlgoCover != True) {
            StaticVarSet(static_name_ + "buyCoverAlgo", 0);
            StaticVarSetText(static_name_ + "buyCoverAlgo_barvalue", "");
        }

        if (AlgoBuy == True AND AlgoCover != True AND StaticVarGet(static_name_ + "buyAlgo") == 0 AND StaticVarGetText(static_name_ + "buyAlgo_barvalue") != lasttime) {
            PlaceSmartOrder("BUY", quantity, position_size);
            if (VoiceAlert == "Enable") Say("Buy Order Triggered");
            _TRACE("API Request: " + sm_api_request);
            _TRACE("API Response: " + sm_api_response);
            StaticVarSetText(static_name_ + "buyAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "buyAlgo", 1);
        } else if (AlgoBuy != True) {
            StaticVarSet(static_name_ + "buyAlgo", 0);
            StaticVarSetText(static_name_ + "buyAlgo_barvalue", "");
        }

        if (AlgoSell == true AND AlgoShort != True AND StaticVarGet(static_name_ + "sellAlgo") == 0 AND StaticVarGetText(static_name_ + "sellAlgo_barvalue") != lasttime) {
            ExitOrder("SELL");
            if (VoiceAlert == "Enable") Say("Sell Exit Order Triggered");
            _TRACE("API Request: " + ex_api_request);
            _TRACE("API Response: " + ex_api_response);
            StaticVarSetText(static_name_ + "sellAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "sellAlgo", 1);
        } else if (AlgoSell != True) {
            StaticVarSet(static_name_ + "sellAlgo", 0);
            StaticVarSetText(static_name_ + "sellAlgo_barvalue", "");
        }

        if (AlgoShort == True AND AlgoSell == True AND StaticVarGet(static_name_ + "ShortSellAlgo") == 0 AND StaticVarGetText(static_name_ + "ShortSellAlgo_barvalue") != lasttime) {
            PlaceSmartOrder("SELL", quantity, -1 * quantity);
            if (VoiceAlert == "Enable") Say("Short Order Triggered");
            _TRACE("API Request: " + sm_api_request);
            _TRACE("API Response: " + sm_api_response);
            StaticVarSetText(static_name_ + "ShortSellAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "ShortSellAlgo", 1);
        } else if (AlgoShort != True OR AlgoSell != True) {
            StaticVarSet(static_name_ + "ShortSellAlgo", 0);
            StaticVarSetText(static_name_ + "ShortSellAlgo_barvalue", "");
        }

        if (AlgoShort == True AND AlgoSell != True AND StaticVarGet(static_name_ + "ShortAlgo") == 0 AND StaticVarGetText(static_name_ + "ShortAlgo_barvalue") != lasttime) {
            PlaceSmartOrder("SELL", quantity, position_size);
            if (VoiceAlert == "Enable") Say("Short Order Triggered");
            _TRACE("API Request: " + sm_api_request);
            _TRACE("API Response: " + sm_api_response);
            StaticVarSetText(static_name_ + "ShortAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "ShortAlgo", 1);
        } else if (AlgoShort != True) {
            StaticVarSet(static_name_ + "ShortAlgo", 0);
            StaticVarSetText(static_name_ + "ShortAlgo_barvalue", "");
        }

        if (AlgoCover == true AND AlgoBuy != True AND StaticVarGet(static_name_ + "CoverAlgo") == 0 AND StaticVarGetText(static_name_ + "CoverAlgo_barvalue") != lasttime) {
            ExitOrder("BUY");
            if (VoiceAlert == "Enable") Say("Short Exit Order Triggered");
            _TRACE("API Request: " + ex_api_request);
            _TRACE("API Response: " + ex_api_response);
            StaticVarSetText(static_name_ + "CoverAlgo_barvalue", lasttime);
            StaticVarSet(static_name_ + "CoverAlgo", 1);
        } else if (AlgoCover != True) {
            StaticVarSet(static_name_ + "CoverAlgo", 0);
            StaticVarSetText(static_name_ + "CoverAlgo_barvalue", "");
        }
    }

    if (EnableButton == "Enable") {
        DrawButton("BE", X0, Y0, X0 + X1, Y0 + 50, colorGreen, colorGreen);
        CursorInBEButton = MouseX >= X0 AND MouseX <= X0 + X1 AND MouseY >= Y0 AND MouseY <= Y0 + 50;
        BEButtonClick = CursorInBEButton AND LBClick;

        DrawButton("BX", X0 + 65, Y0, X0 + X1 + 65, Y0 + 50, colorRed, colorRed);
        CursorInBXButton = MouseX >= X0 + 65 AND MouseX <= X0 + X1 + 65 AND MouseY >= Y0 AND MouseY <= Y0 + 50;
        BXButtonClick = CursorInBXButton AND LBClick;

        DrawButton("SE", X0, Y0 + 55, X0 + X1, Y0 + 105, colorRed, colorRed);
        CursorInSEButton = MouseX >= X0 AND MouseX <= X0 + X1 AND MouseY >= Y0 + 55 AND MouseY <= Y0 + 105;
        SEButtonClick = CursorInSEButton AND LBClick;

        DrawButton("SX", X0 + 65, Y0 + 55, X0 + X1 + 65, Y0 + 105, colorGreen, colorGreen);
        CursorInSXButton = MouseX >= X0 + 65 AND MouseX <= X0 + X1 + 65 AND MouseY >= Y0 + 55 AND MouseY <= Y0 + 105;
        SXButtonClick = CursorInSXButton AND LBClick;

        DrawButton("CLOSE ALL", X0, Y0 + 110, X0 + X1 + 65, Y0 + 155, colorRed, colorRed);
        CursorInCXButton = MouseX >= X0 AND MouseX <= X0 + X1 + 65 AND MouseY >= Y0 + 110 AND MouseY <= Y0 + 155;
        CXButtonClick = CursorInCXButton AND LBClick;

        if (BEButtonClick AND StaticVarGet(static_name_ + "BEAlgo") == 0) {
            PlaceSmartOrder("BUY", quantity, position_size);
            if (VoiceAlert == "Enable") {
                Say("Buy Order Triggered");
            }
            _TRACE("API Request: " + postData);
            StaticVarSet(static_name_ + "BEAlgo", 1);
        } else {
            StaticVarSet(static_name_ + "BEAlgo", 0);
        }

        if (BXButtonClick AND StaticVarGet(static_name_ + "BXAlgo") == 0) {
            PlaceSmartOrder("SELL", quantity, position_size);
            if (VoiceAlert == "Enable") {
                Say("Sell Order Triggered");
            }
            _TRACE("API Request: " + postData);
            StaticVarSet(static_name_ + "BXAlgo", 1);
        } else {
            StaticVarSet(static_name_ + "BXAlgo", 0);
        }

        if (SEButtonClick AND StaticVarGet(static_name_ + "SEAlgo") == 0) {
            PlaceSmartOrder("SELL", quantity, position_size);
            if (VoiceAlert == "Enable") {
                Say("Short Order Triggered");
            }
            _TRACE("API Request: " + postData);
            StaticVarSet(static_name_ + "SEAlgo", 1);
        } else {
            StaticVarSet(static_name_ + "SEAlgo", 0);
        }

        if (SXButtonClick AND StaticVarGet(static_name_ + "SXAlgo") == 0) {
            PlaceSmartOrder("BUY", quantity, position_size);
            if (VoiceAlert == "Enable") {
                Say("Cover Order Triggered");
            }
            _TRACE("API Request: " + postData);
            StaticVarSet(static_name_ + "SXAlgo", 1);
        } else {
            StaticVarSet(static_name_ + "SXAlgo", 0);
        }

        if (CXButtonClick AND StaticVarGet(Name() + GetChartID() + "CXAlgo") == 0) {
            SquareOffAll();
            if (VoiceAlert == "Enable") {
                Say("Square Off All Triggered");
            }
            _TRACE("API Request: " + postData);
            StaticVarSet(Name() + GetChartID() + "CXAlgo", 1);
        } else {
            StaticVarSet(Name() + GetChartID() + "CXAlgo", 0);
        }
    }
}

_SECTION_END();

2 Likes

Could you please share with me BrokerWS.EXE
For next testing !

@nsm51 @rajandran @Tomasz

I want to know is the following code is efficient or needs fine tuning, `


// ============================================================
// PART 1: Your Signal Definitions and Core Parameters
// ============================================================
_SECTION_BEGIN("Strategy Signals & Core Parameters");

// --- Your Signal Conditions ---
// Ensure buy, Sell, etc  calculated *before* this section.
BuyCond =  .............;
ShortCond = .................;
SellCond = ..........; // Exit Long condition
CoverCond = .........; // Exit Short condition

// --- Core Parameters Needed by State Logic & OpenAlgo Functions ---
pApiKey = ParamStr("OpenAlgo API Key", "******");
pStrategyName = ParamStr("Strategy", "Amibroker_StateManaged"); // Give unique name
pSymbol = Name(); // <-- AUTOMATIC SYMBOL from Chart Name()
pExchange = ParamList("Exchange", "NFO|NSE|BSE|MCX|CDS", 0); // Default NFO
pPriceType = ParamStr("Price Type", "MARKET");
pProduct = ParamList("Product", "MIS|NRML|CNC", 0); // Default MIS
pQuantity = Param("Quantity", 1, 1, 1000, 1);


pTelegramEnable = ParamToggle("Send Telegram Alerts?", "No|Yes", 1); // Default to Yes
pTelegramAPI_ID = ParamStr("Telegram Bot API ID", "......"); // e.g., 7123456789:ABCdefGhIJKkoNopQrstuVWxyz
pTelegramChatID = ParamStr("Telegram Chat ID", "......"); // e.g., -1001234567890 or your personal ID

// --- State Management Control ---
pEnableTrading = ParamList("Enable Trading (Algo Mode)", "Disable|Enable", 0); // Default Enable

_SECTION_END();

// ============================================================
// PART 2: OpenAlgo Module Code (Adapted v2.0)
// ============================================================
_SECTION_BEGIN("OpenAlgo SmartOrder Trading Module - State Managed");

RequestTimedRefresh(1, False); // Crucial for intra-bar updates

// --- OpenAlgo Parameters (using variables from PART 1) ---
apikey = pApiKey;
strategy = pStrategyName;
symbol = pSymbol; // Uses the variable derived from Name()
exchange = pExchange;
pricetype = pPriceType;
product = pProduct;
quantity = pQuantity; // Note: quantity param might be redundant now, pQuantity is used directly

host = ParamStr("Host", "http://127.0.0.1:5000");
ver = ParamStr("API Version", "v1");

VoiceAlert = ParamList("Voice Alert", "Disable|Enable", 1);
EnableButton = ParamList("Button Trading", "Disable|Enable", 0);

// --- Button Trading Variables & Procedure Definition (MOVED OUTSIDE 'IF') ---
// Define these *before* the 'if (EnableButton == "Enable")' block

// Button Coordinates and Mouse Setup
X0 = 20;
Y0 = Status("pxheight") - 160; // Position buttons near bottom-left
X1 = 60; // Button Width
BtnHeight = 45; // Button Height
VSpace = 5; // Vertical space between buttons

LBClick = GetCursorMouseButtons() == 9;	// Left Mouse Button Click
MouseX  = Nz(GetCursorXPosition(1));	// Current Mouse X
MouseY  = Nz(GetCursorYPosition(1));	// Current Mouse Y

// Procedure to Draw Buttons (Defined Globally)
procedure DrawButton (Text, x1, y1, x2, y2, colorFrom, colorTo)
{
    GfxSetOverlayMode(0);
    GfxSelectFont("Verdana", 9, 700);
    GfxSetBkMode(1); // Transparent background for text
    GfxGradientRect(x1, y1, x2, y2, colorFrom, colorTo); // Draw button background
    GfxDrawText(Text, x1, y1, x2, y2, 32|1|4|16); // Draw text (centered)
}

// --- End of Moved Section ---


// Construct URL base
bridgeurl = host + "/api/" + ver;

// Static variable base name (used in state management)
static_name_base = symbol+GetChartID()+interval(2)+strategy; // Use symbol in name for uniqueness

// OpenAlgo Dashboard GFX code
//---------------------------------------------------
GfxSelectFont( "BOOK ANTIQUA", 14, 100 );
GfxSetBkMode( 1 );
static_name_algo = static_name_base+"_algostatus_flag";
if(pEnableTrading == "Enable")
{
    AlgoStatus = "Algo Enabled"; GfxSetTextColor( colorGreen );
    GfxTextOut( "Algostatus ["+symbol+"] : "+AlgoStatus , 20, 40);
    if(Nz(StaticVarGet(static_name_algo),0)!=1) { _TRACE("Algo Status ["+symbol+"]: Enabled"); StaticVarSet(static_name_algo, 1); }
}
else // Disabled
{
    AlgoStatus = "Algo Disabled"; GfxSetTextColor( colorRed );
    GfxTextOut( "Algostatus ["+symbol+"] : "+AlgoStatus , 20, 40);
    if(Nz(StaticVarGet(static_name_algo),0)!=0) { _TRACE("Algo Status ["+symbol+"]: Disabled"); StaticVarSet(static_name_algo, 0); }
}
//---------------------------------------------------


   // --- REVISED OpenAlgo Communication Function (using insights from v1.0) ---
// Consolidated function using /placesmartorder endpoint for entries and exits
function SendOpenAlgoOrder(action_param, qty_param, position_size_param)
{
    // Ensure quantity and position_size are passed as strings
    qty_str = NumToStr(qty_param, 1.0, False); // No decimal places, no comma
    pos_size_str = NumToStr(position_size_param, 1.0, False);

    postData = "{\"apikey\": \"" + apikey + "\", " +
               "\"strategy\": \"" + strategy + "\", " +
               "\"symbol\": \"" + symbol + "\", " +
               "\"action\": \"" + action_param + "\", " +
               "\"exchange\": \"" + exchange + "\", " +
               "\"pricetype\": \"" + pricetype + "\", " +
               "\"product\": \"" + product + "\", " +
               "\"quantity\": \"" + qty_str + "\", " +
               "\"position_size\": \"" + pos_size_str + "\"}";

    headers = "Content-Type: application/json\r\n" +
              "Accept-Encoding: gzip, deflate\r\n";
    InternetSetHeaders(headers);

    // --- MODIFIED TRACE --- Use _TRACE for safety with potentially complex strings
    _TRACE("["+symbol+"] OpenAlgo Request: Action=" + action_param + ", Qty=" + qty_str + ", PosSize=" + pos_size_str + ", Data=" + postData);
    // --- END MODIFIED TRACE ---

    ih = InternetPostRequest(bridgeurl + "/placesmartorder", postData);

    response = "";
    retval = -2; // Default to connection failure

    if (ih) {
        while ((line = InternetReadString(ih)) != "") {
            response += line;
        }
        // --- MODIFIED TRACE --- Use _TRACE for safety with external response
        _TRACE("["+symbol+"] OpenAlgo Response: " + response);
        // --- END MODIFIED TRACE ---

        // Check response for success indicator (adjust based on actual OpenAlgo response)
        // This check might need refinement based on exact success/failure JSON structure
        if (StrFind(response, "\"status\"") AND StrFind(response, "\"success\""))
        {
           if (VoiceAlert == "Enable") Say(symbol + " " + action_param + " Order Success.");
           retval = 0; // Indicate success
        }
        else
        {
           if (VoiceAlert == "Enable") Say(symbol + " " + action_param + " Order Failed.");
           // --- MODIFIED TRACE --- Use _TRACE for safety with external response
           _TRACE("["+symbol+"] OpenAlgo Order Failure Response: " + response);
           // --- END MODIFIED TRACE ---
            retval = -1; // Indicate failure reported by API
        }
        InternetClose(ih);
    } else {
         // --- MODIFIED TRACE --- Use _TRACEF only with known safe formats, otherwise _TRACE
        _TRACE("["+symbol+"] Failed to send OpenAlgo request (InternetPostRequest failed). Action=" + action_param);
         // --- END MODIFIED TRACE ---
        if (VoiceAlert == "Enable") Say(symbol + " " + action_param + " Request Failed.");
        // retval remains -2
    }
    return retval; // Return status (0 success, -1 API fail, -2 connection fail)
}
////////////////////////////



// --- NEW Telegram Communication Function ---
function SendTelegramAlert(message_text)
{
    // Basic URL encoding for spaces - might need more robust encoding for other special chars
    encoded_message = StrReplace(message_text, " ", "+");
    RequestURL = "https://api.telegram.org/bot" + pTelegramAPI_ID + "/sendMessage?chat_id=" + pTelegramChatID + "&text=" + encoded_message;

    _TRACE("["+symbol+"] Sending Telegram Alert: " + message_text);
    ih = InternetOpenURL( RequestURL ); // Use InternetOpenURL for GET request
    retval = -2; // Connection failure default

    if ( ih )
    {
        // Optional: Read response to confirm success from Telegram API
        response = "";
         while( ( line = InternetReadString( ih ) ) != "" ) response += line;
         InternetClose( ih );
         _TRACE("["+symbol+"] Telegram Response: " + response);
         // Simple check - Telegram success usually contains "ok":true
         if(StrFind(response, "\"ok\":true"))
         {
             retval = 0; // Success
         }
         else
         {
             _TRACE("["+symbol+"] Telegram Alert Send Failed (API Response): " + response);
             retval = -1; // API failure
         }
    }
    else
    {
        _TRACE( "["+symbol+"] ERROR: Failed to open URL for Telegram Alert. Check API/Chat ID/Connection. URL: " + RequestURL );
         // retval remains -2
    }
     return retval; // 0 success, -1 API fail, -2 connection fail
}

//////////////////////
// Button Codes..... 
_SECTION_END(); // End of OpenAlgo Module Section



// ============================================================
// PART 3: State Management Execution Logic (Combined Signals + Bar Lock)
// ============================================================
_SECTION_BEGIN("State Management Execution");

// Run state logic if EITHER system is enabled
if (pEnableTrading == "Enable" OR pTelegramEnable == 1)
{
    // --- State Management Variables ---
    tradeStateVar = static_name_base + "_TradeState";
    signalSentVar = static_name_base + "_SignalSentFlag";
    lastEntryBarIndexVar = static_name_base + "_LastEntryBarIdx"; // NEW Static Var Name

    currentTradeState = Nz(StaticVarGet(tradeStateVar), 0);
    signalSentFlag = Nz(StaticVarGet(signalSentVar), 0);
    lastEntryBarIndex = Nz(StaticVarGet(lastEntryBarIndexVar), -1); // NEW: Get last entry bar index, default -1

    // Get current bar index (use LastValue for comparison)
    currentBarIndex = BarIndex();
    lvCurrentBarIndex = LastValue(currentBarIndex); // Get scalar value for current bar

    // --- Determine Target State ---
    targetTradeState = currentTradeState;
    if (LastValue(BuyCond) AND currentTradeState <= 0) targetTradeState = 1;
    else if (LastValue(SellCond) AND currentTradeState == 1) targetTradeState = 0;
    else if (LastValue(ShortCond) AND currentTradeState >= 0) targetTradeState = -1;
    else if (LastValue(CoverCond) AND currentTradeState == -1) targetTradeState = 0;

    // --- Signal Generation & Sending ---
    stateChanged = (targetTradeState != currentTradeState);

    if (stateChanged AND signalSentFlag == 0)
    {
        action = "";
        triggerProcessed = False;
        orderResult = -99;
        telegramResult = -99;
        isEntrySignal = False; // NEW: Flag to check if it was an entry

        // Determine Action and Send Signals
        if (targetTradeState == 1) // Buy Entry
        {
            // NEW Check: Only allow entry if it's a new bar since the last entry
            if (lvCurrentBarIndex != lastEntryBarIndex)
            {
                action = "BUY";
                BuyMsg = "Buy Alert: " + symbol + " @ " + NumToStr( LastValue( C ), 1.2 ) + " | Time: " + LastValue( TimeNum() );
                if(pEnableTrading == "Enable") orderResult = SendOpenAlgoOrder(action_param = "BUY", qty_param = pQuantity, position_size_param = pQuantity);
                if(pTelegramEnable == 1) telegramResult = SendTelegramAlert(BuyMsg);
                triggerProcessed = True;
                isEntrySignal = True; // Mark as an entry signal
            }
            else
            {
                 _TRACE("["+symbol+"] State Logic: Buy condition met but entry blocked on same bar (BarIdx: " + lvCurrentBarIndex + ")");
            }
        }
        else if (targetTradeState == -1) // Short Entry
        {
             // NEW Check: Only allow entry if it's a new bar since the last entry
             if (lvCurrentBarIndex != lastEntryBarIndex)
             {
                action = "SHORT";
                ShortMsg = "Short Alert: " + symbol + " @ " + NumToStr( LastValue( C ), 1.2 ) + " | Time: " + LastValue( TimeNum() );
                if(pEnableTrading == "Enable") orderResult = SendOpenAlgoOrder(action_param = "SELL", qty_param = pQuantity, position_size_param = -pQuantity);
                if(pTelegramEnable == 1) telegramResult = SendTelegramAlert(ShortMsg);
                triggerProcessed = True;
                isEntrySignal = True; // Mark as an entry signal
             }
             else
             {
                  _TRACE("["+symbol+"] State Logic: Short condition met but entry blocked on same bar (BarIdx: " + lvCurrentBarIndex + ")");
             }
        }
        else if (targetTradeState == 0 AND currentTradeState == 1) // Sell Exit
        {
            // Exits are NOT restricted by the bar lock
            action = "SELL";
            SellMsg = "Sell Exit Alert: " + symbol + " @ " + NumToStr( LastValue( C ), 1.2 ) + " | Time: " + LastValue( TimeNum() );
            if(pEnableTrading == "Enable") orderResult = SendOpenAlgoOrder(action_param = "SELL", qty_param = 0, position_size_param = 0);
            if(pTelegramEnable == 1) telegramResult = SendTelegramAlert(SellMsg);
            triggerProcessed = True;
            // isEntrySignal remains False
        }
        else if (targetTradeState == 0 AND currentTradeState == -1) // Cover Exit
        {
             // Exits are NOT restricted by the bar lock
            action = "COVER";
            CoverMsg = "Cover Exit Alert: " + symbol + " @ " + NumToStr( LastValue( C ), 1.2 ) + " | Time: " + LastValue( TimeNum() );
            if(pEnableTrading == "Enable") orderResult = SendOpenAlgoOrder(action_param = "BUY", qty_param = 0, position_size_param = 0);
            if(pTelegramEnable == 1) telegramResult = SendTelegramAlert(CoverMsg);
            triggerProcessed = True;
            // isEntrySignal remains False
        }

        // Process Result and Update State (Only if a valid state change was triggered)
        if (triggerProcessed)
        {
            // Set flag: Signal SENT/Attempted for this state change event
            StaticVarSet( signalSentVar, 1 );
            _TRACE( "["+symbol+"] State Logic: Processed " + action + " signal event." );

            // Update persisted state *after* attempting send and setting flag
            StaticVarSet( tradeStateVar, targetTradeState );
            _TRACE("["+symbol+"] State Logic: State changed from " + NumToStr(currentTradeState,1.0) + " to " + NumToStr(targetTradeState,1.0));

            // NEW: Update the Last Entry Bar Index *only if* this was an entry signal
            if (isEntrySignal)
            {
                 StaticVarSet( lastEntryBarIndexVar, lvCurrentBarIndex );
                 _TRACE("["+symbol+"] State Logic: Updated Last Entry Bar Index to " + lvCurrentBarIndex);
            }
        }

    } // End if(stateChanged AND signalSentFlag == 0)

    // --- Reset Signal Sent Flag ---
    if ( !stateChanged OR targetTradeState == currentTradeState )
    {
        StaticVarSet( signalSentVar, 0 );
    }

} // End if(pEnableTrading == "Enable" OR pTelegramEnable == 1)

_SECTION_END(); // End of State Management Section
`
1 Like