Page 3 of 4
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:30 am
by PTScalper
Implementation details:
Store the latest alert in a global variable or simple queue.
Provide clear setup instructions.
Ensure security (briefly mention Ngrok or IP whitelisting).
Structure:
Direct opening.
Python Code block.
Setup instructions.
Follow up about setting up ngrok or full deployment.
Let's refine the Python code:
Code: Select all
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
# Global variable to store the latest signal
latest_signal = {}
@app.route('/webhook', methods=['POST'])
def webhook():
global latest_signal
try:
# Parse the JSON payload from TradingView
data = request.get_json(force=True)
# Basic validation to ensure required fields exist
if 'action' in data and 'symbol' in data:
latest_signal = data
print(f"✅ New Signal Received: {data}")
return jsonify({"status": "success", "message": "Signal stored"}), 200
else:
print("❌ Invalid payload format")
return jsonify({"status": "error", "message": "Invalid payload"}), 400
except Exception as e:
print(f"❌ Error processing webhook: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/get_latest_alert', methods=['GET'])
def get_latest_alert():
# MT5 will hit this endpoint every second
global latest_signal
if latest_signal:
return jsonify(latest_signal), 200
else:
# Return an empty JSON object if no signal has been received yet
return jsonify({}), 200
if __name__ == '__main__':
# Run the server on port 5000 (localhost)
app.run(host='0.0.0.0', port=5000)
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:31 am
by PTScalper
Are there any specifics? Let's clear the signal after reading?
No, the MT5 script handles duplicate execution prevention via json_string != last_processed_signal. So keeping the latest state is fine, but clearing it after read is safer in case of MT5 restart. Actually, let's keep it simple. The EA logic already has if(json_string != last_processed_signal).
I'll provide the Python code and explain how to bridge the gap using Ngrok (to give TradingView a public URL while keeping the local port 5000 open for MT5).
Let's structure the answer.
No labeled closings. Direct lead in.
"This Python Flask server acts as the central router between the internet and your local MetaTrader 5 terminal. It listens for the HTTP POST webhook from TradingView, validates the JSON payload, and holds it in memory so your MT5 EA can fetch it via a standard GET request."
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:34 am
by PTScalper
Here is the MQL4 version. While the underlying architecture—polling the Python Flask server every second—remains exactly the same, MT4 handles order execution and network requests differently than MT5.
Most notably, MT4 does not have the CTrade class, meaning you must manage order tickets, slippage, and price normalization manually using OrderSend() and OrderClose().
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:34 am
by PTScalper
The MQL4 Execution Logic
Save this as TV_Webhook_Receiver.mq4 in your MQL4/Experts folder. Just like the MT5 version, it relies on the open-source JAson.mqh library (which is fully backwards-compatible with MT4) to parse the JSON string.
Code: Select all
//+------------------------------------------------------------------+
//| TV_Webhook_Receiver.mq4|
//+------------------------------------------------------------------+
#property strict
#include <JAson.mqh> // Download from MQL4/MQL5 CodeBase
string server_url = "http://localhost:5000/get_latest_alert";
string last_processed_signal = "";
int magic_number = 777777; // Unique ID to manage only TV webhook trades
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Enable polling every 1 second
EventSetTimer(1);
Print("EA Initialized. Listening for TradingView signals...");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
}
//+------------------------------------------------------------------+
//| Expert timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
string headers = "";
string result_headers = "";
char post[], result[];
// MQL4 uses a slightly different WebRequest parameter structure than MT5
int res = WebRequest("GET", server_url, headers, 500, post, result, result_headers);
if(res == 200)
{
string json_string = CharArrayToString(result);
// Prevent duplicate execution of the same signal
if(json_string != last_processed_signal && json_string != "")
{
ParseAndExecute(json_string);
last_processed_signal = json_string;
}
}
}
//+------------------------------------------------------------------+
//| Parse JSON and Execute Trade |
//+------------------------------------------------------------------+
void ParseAndExecute(string json_payload)
{
CJAVal json;
// Deserialize the JSON string sent by TradingView
if(json.Deserialize(json_payload))
{
string action = json["action"].ToStr();
string symbol = json["symbol"].ToStr();
// Fetch current broker pricing and digit structure
double ask = MarketInfo(symbol, MODE_ASK);
double bid = MarketInfo(symbol, MODE_BID);
int digits = (int)MarketInfo(symbol, MODE_DIGITS);
// Placeholder for Rule 5 position sizing
double lot_size = 0.1;
if(action == "long")
{
// MT4 requires strict price normalization before execution
double sl = NormalizeDouble(json["sl"].ToDbl(), digits);
double tp = NormalizeDouble(json["tp"].ToDbl(), digits);
int ticket = OrderSend(symbol, OP_BUY, lot_size, ask, 3, sl, tp, "TV Long Sweep", magic_number, 0, clrGreen);
if(ticket < 0) Print("OrderSend Error: ", GetLastError());
else Print("Executed Long on ", symbol);
}
else if(action == "short")
{
double sl = NormalizeDouble(json["sl"].ToDbl(), digits);
double tp = NormalizeDouble(json["tp"].ToDbl(), digits);
int ticket = OrderSend(symbol, OP_SELL, lot_size, bid, 3, sl, tp, "TV Short Sweep", magic_number, 0, clrRed);
if(ticket < 0) Print("OrderSend Error: ", GetLastError());
else Print("Executed Short on ", symbol);
}
else if(action == "exit_long" || action == "exit_short")
{
// Iterate backwards through the order pool to safely close positions
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == symbol && OrderMagicNumber() == magic_number)
{
// Determine correct closing price based on order direction
double close_price = (OrderType() == OP_BUY) ? MarketInfo(symbol, MODE_BID) : MarketInfo(symbol, MODE_ASK);
bool closed = OrderClose(OrderTicket(), OrderLots(), close_price, 3, clrGray);
if(!closed) Print("OrderClose Error: ", GetLastError());
else Print("Closed position on ", symbol, " due to TV exit signal.");
}
}
}
}
}
}
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:35 am
by PTScalper
Critical MT4 Setup Steps
Allow WebRequest: Just like MT5, you must go to Tools > Options > Expert Advisors, check "Allow WebRequest for listed URL," and add
http://localhost:5000 so MT4 has permission to ping the Python script.
Auto Trading: Ensure the "Auto Trading" button at the top of your MT4 terminal is green, and that "Allow live trading" is checked in the EA's settings when you attach it to a chart.
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:37 am
by PTScalper
And this is the break down how to expose this localhost Flask server to TradingView using Ngrok so I can paste a public URL into my TradingView alerts.
Ngrok bridges the gap between TradingView's cloud servers and your local machine by creating a secure tunnel straight through your firewall.
1.) Install the Ngrok Agent - Download the executable from ngrok.com, extract the file, and open your terminal or command prompt in that directory.
2.) Authenticate the CLI:Required once per machine.Create a free ngrok account. In your dashboard, locate your personal Authtoken and run
this command to link your local agent:
Code: Select all
ngrok config add-authtoken YOUR_TOKEN_HERE
3.) Start the HTTP Tunnel:Must run concurrently with Flask.With your Python Flask server already running on port 5000, execute:
Leave this terminal window completely alone. Closing it will immediately kill the connection.
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:38 am
by PTScalper
4.Route the TradingView Alert:
Ngrok will display a status dashboard in your terminal. Locate the Forwarding row and copy the generated https:// address.
In TradingView, open your alert settings, enable Webhook URL, and paste the address. You MUST append your specific Flask route (/webhook) to the end of the URL so the payload goes to the right place:[
https://1a2b-34-56-78-90.ngrok-free.app/webhook](
https://1a2b-34-56-78-90.ngrok-free.app/webhook)
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:38 am
by PTScalper
TradingView strictly requires webhooks to be sent to port 80 or 443. Ngrok handles this automatically by generating a standard HTTPS (port 443) URL and securely routing that incoming traffic backward through your firewall to your local port 5000. One major operational constraint: on the free tier, Ngrok generates a completely new, randomized URL every time you restart the tunnel. If your machine reboots or you close the terminal window, you will have to manually update your active TradingView alerts with the newly generated address.
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:39 am
by PTScalper
Because your Ngrok URL is publicly accessible, unauthorized bots actively scan these endpoints looking for open trading bridges. To lock them out, we must implement a two-layer defense system: an IP Allowlist to drop unauthorized traffic at the door, and a Payload Passphrase to authenticate the actual JSON data.
Here is the secured version of your Flask server:
Code: Select all
from flask import Flask, request, jsonify
app = Flask(__name__)
# Holds the most recent webhook payload in memory
latest_signal = {}
# Layer 1: TradingView's official webhook IP addresses
TRADINGVIEW_IPS = [
"52.89.214.238",
"34.212.75.30",
"54.218.53.128",
"52.32.178.7"
]
# Layer 2: Your custom secret PIN
SECRET_PASSPHRASE = "FairmanBlueprint2026!"
@app.route('/webhook', methods=['POST'])
def webhook():
global latest_signal
# --- SECURITY LAYER 1: IP ALLOWLIST ---
# Because Ngrok proxies the connection locally, request.remote_addr is always 127.0.0.1.
# We must extract the true origin IP from the X-Forwarded-For header.
forwarded_for = request.headers.get('X-Forwarded-For', '')
client_ip = forwarded_for.split(',')[0].strip() if forwarded_for else request.remote_addr
if client_ip not in TRADINGVIEW_IPS:
print(f"⛔ Intrusion blocked: Unauthorized IP {client_ip}")
return jsonify({"error": "Unauthorized IP"}), 403
try:
data = request.get_json(force=True)
# --- SECURITY LAYER 2: PASSPHRASE ---
if data.get("passphrase") != SECRET_PASSPHRASE:
print(f"⛔ Intrusion blocked: Invalid Passphrase from {client_ip}")
return jsonify({"error": "Unauthorized payload"}), 403
# Execute trade logic
if 'action' in data and 'symbol' in data:
latest_signal = data
print(f"⚡ Secure Signal Processed: {data['action'].upper()} on {data['symbol']}")
return jsonify({"status": "success", "message": "Signal queued for MT4"}), 200
else:
return jsonify({"error": "Invalid payload structure"}), 400
except Exception as e:
print(f"❌ Webhook Error: {str(e)}")
return jsonify({"error": "Failed to process payload"}), 500
@app.route('/get_latest_alert', methods=['GET'])
def get_latest_alert():
global latest_signal
if latest_signal:
return jsonify(latest_signal), 200
else:
return jsonify({}), 200
if __name__ == '__main__':
print("🚀 Secure Webhook Server running on http://localhost:5000")
app.run(host='0.0.0.0', port=5000)
Re: Your Trading Plan Needs These 7 Non-Negotiable Sections
Posted: Wed Sep 23, 2026 8:41 am
by PTScalper
How the Defenses Work
The IP Allowlist (The Guest List): TradingView publishes four official IP addresses that their servers use to dispatch webhooks. The script checks the incoming IP against this list. If a random bot scans your Ngrok URL, its IP won't match, and the server instantly drops the connection with a 403 Forbidden error.
The Passphrase (The PIN Code): IP addresses can technically be spoofed in HTTP headers. The ultimate defense is requiring a hardcoded password inside the JSON payload itself. If an attacker manages to bypass the IP check but doesn't know your exact passphrase, the script rejects the payload before it ever reaches MT4.
Updating Your Pine Script
To make this work, you must update the JSON construction strings in the Execution block of your Pine Script so TradingView includes your secret passphrase in every alert it sends.
Simply inject "passphrase": "FairmanBlueprint2026!" at the very beginning of your JSON strings:
Code: Select all
// Construct dynamic JSON for Long Entry
string long_json = '{"passphrase": "FairmanBlueprint2026!", "action": "long", "symbol": "' + syminfo.ticker + '", "price": ' + str.tostring(close) + ', "sl": ' + str.tostring(long_sl) + ', "tp": ' + str.tostring(long_tp) + '}'
Once this is updated on the chart and your alerts are recreated, your execution bridge is completely locked down.