Guides, manuals and platform references.
AlgoWay can receive trading instructions from any Python strategy that can send an HTTPS POST request. The strategy does not need a separate broker library for every destination. It sends one universal JSON message to the user's personal AlgoWay webhook, and AlgoWay routes the command to the selected MetaTrader 5, cTrader, TradeLocker, Match-Trader, DXtrade, Interactive Brokers, prop firm or cryptocurrency account.
This guide provides the complete integration model: the request method, required HTTP header, AlgoWay JSON fields, a minimal Python request, a complete EMA crossover strategy with entry and exit signals, logging, error handling, testing rules and the work required from a Python strategy platform that wants to offer AlgoWay as an execution layer.
Python strategy | HTTPS POST with JSON | Personal AlgoWay webhook | Validation and routing | Broker, prop firm or exchange
Last updated: 2026-07-31 | Author: AlgoWay
A Python bot sends a normal POST request to the personal AlgoWay webhook URL. The body must be valid JSON and the request must declare Content-Type: application/json.
POST https://algoway.co/your-webhook-uuid
Content-Type: application/json
{
"platform_name": "metatrader5",
"ticker": "EURUSD",
"order_action": "buy",
"order_contracts": "0.01"
}
The same request pattern can be used from a standalone Python bot, Jupyter notebook, cloud function, VPS service, research environment, backtesting platform or hosted Python Algo Playground. The strategy only needs outbound HTTPS access.
The cleanest architecture separates strategy research from trade execution.
| Layer | Responsibility |
|---|---|
| Python strategy | Receives market data, calculates indicators, evaluates rules and decides whether to buy, sell, close or do nothing. |
| AlgoWay client or adapter | Converts the strategy decision into universal AlgoWay JSON, sends the HTTPS request and logs the response. |
| AlgoWay webhook | Identifies the user route, validates the payload and sends the instruction to the selected destination. |
| Execution destination | MetaTrader 5, cTrader, TradeLocker, Match-Trader, DXtrade, Interactive Brokers, a prop firm environment or a supported crypto exchange executes or rejects the order. |
This design prevents the Python strategy from becoming dependent on one broker API. The same EMA crossover, machine-learning model, portfolio engine or custom signal generator can use the same AlgoWay client while the user changes the destination route in AlgoWay.
A Python environment needs only a small set of capabilities:
No broker password, exchange API key or MetaTrader credentials are sent by the Python strategy. Those credentials remain in the corresponding AlgoWay route configuration.
The standard request uses the following components:
| Component | Required value | Purpose |
|---|---|---|
| Method | POST |
Sends one trading instruction to the webhook. |
| URL | Personal AlgoWay webhook URL | Identifies the user's webhook route. |
| Header | Content-Type: application/json |
Tells AlgoWay that the request body contains JSON. |
| Optional header | Accept: application/json |
States that the client can process a JSON response. |
| Optional header | User-Agent: AlgoWay-Python-Client/1.0 |
Makes source identification easier in network and application logs. |
| Authorization | No separate Bearer token for the standard personal webhook | The unique webhook URL identifies the route and must be protected as a secret. |
| Body | One valid JSON object | Contains the platform, symbol, action, quantity and optional order parameters. |
Content-Type: application/json. Sending JSON as form data, plain text or an unlabelled byte stream can cause validation or HTTP 415 errors.
The minimum useful payload contains four fields:
{
"platform_name": "metatrader5",
"ticker": "EURUSD",
"order_action": "buy",
"order_contracts": "0.01"
}
| Field | Meaning |
|---|---|
platform_name |
The AlgoWay connector that should process the command, for example metatrader5, ctrader, tradelocker, dxtrade, binance, bybit or okx. |
ticker |
The symbol expected by the destination route or mapped by the connected platform. |
order_action |
The position instruction, normally buy, sell or flat. |
order_contracts |
The quantity, contracts, units or lot value interpreted by the selected destination. |
The quantity model is destination-specific. A value of 1 may mean one lot, one contract, one coin or one unit depending on the selected route and its settings. Always test the route with the smallest permitted size.
The platform_name value must identify the destination connector configured for the webhook.
| Platform | platform_name |
|---|---|
| MetaTrader 5 | metatrader5 |
| TradeLocker | tradelocker |
| Match-Trader | matchtrader |
| DXtrade | dxtrade |
| cTrader | ctrader |
| Capital.com | capitalcom |
| Alpaca | alpaca |
| Tradovate | tradovate |
| ProjectX | projectx |
| Interactive Brokers | ibkr |
| Binance | binance |
| Bybit | bybit |
| OKX | okx |
| BitMEX | bitmex |
| BitMart | bitmart |
| Bitget | bitget |
| BingX | bingx |
| Coinbase | coinbase |
| MEXC | mexc |
Use the value assigned to the user's actual AlgoWay route. Do not copy a platform name from an unrelated example.
A universal Python integration should model the complete position lifecycle, not only entries.
| Strategy event | AlgoWay action | Result |
|---|---|---|
| Open a long position | buy |
Sends a long entry instruction. |
| Open a short position | sell |
Sends a short entry instruction. |
| Close the active position | flat |
Requests closure according to the destination and AlgoWay trade mode. |
| Reverse from short to long | flat, then buy |
Uses two explicit signals so the exit and new entry are visible in logs. |
| Reverse from long to short | flat, then sell |
Uses two explicit signals so the exit and new entry are visible in logs. |
The EMA example below uses explicit flat followed by the new entry when the direction changes. This makes the workflow easy to understand and inspect. The selected AlgoWay connector and trade mode still determine the final execution behavior.
This dependency-free example uses only the Python standard library. It sends one buy instruction and prints the HTTP status and response body.
import json
from urllib.request import Request, urlopen
webhook_url = "https://algoway.co/your-webhook-uuid"
payload = {
"platform_name": "metatrader5",
"ticker": "EURUSD",
"order_action": "buy",
"order_contracts": "0.01"
}
request = Request(
webhook_url,
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "AlgoWay-Python-Client/1.0"
},
method="POST"
)
with urlopen(request, timeout=7) as response:
print(response.status, response.read().decode("utf-8"))
Replace the example URL with the personal webhook URL copied from the AlgoWay dashboard. Do not publish the real URL in source repositories, screenshots, public notebooks or support forums.
The following template demonstrates a complete, understandable integration. A fast EMA crossing above a slow EMA produces a long signal. A fast EMA crossing below a slow EMA produces a short signal. When the strategy changes direction, it first sends flat for the existing position and then sends the new entry.
The template includes:
AlgoWayClient;ALGOWAY_DRY_RUN is enabled unless it is explicitly set to false. The example logs signals without sending them until the operator deliberately enables webhook delivery.
from __future__ import annotations
import json
import logging
import os
import time
import uuid
from dataclasses import dataclass
from typing import Any, Literal
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
Action = Literal["buy", "sell", "flat"]
Position = Literal["long", "short", "flat"]
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
logger = logging.getLogger("algoway.python.ema")
@dataclass(frozen=True)
class AlgoWayConfig:
webhook_url: str
platform_name: str = "metatrader5"
timeout_seconds: float = 7.0
dry_run: bool = True
class AlgoWayClient:
def __init__(self, config: AlgoWayConfig) -> None:
if not config.webhook_url:
raise ValueError("AlgoWay webhook URL is required")
self.config = config
def send_signal(
self,
ticker: str,
action: Action,
quantity: float,
price: float | None = None,
sl_price: float | None = None,
tp_price: float | None = None,
trailing_pips: float | None = None,
comment: str | None = None
) -> dict[str, Any]:
if not ticker:
raise ValueError("Ticker is required")
if quantity <= 0:
raise ValueError("Quantity must be greater than zero")
request_id = uuid.uuid4().hex
payload: dict[str, Any] = {
"platform_name": self.config.platform_name,
"ticker": ticker,
"order_action": action,
"order_contracts": str(quantity),
"comment": comment or f"python_ema:{request_id}"
}
if price is not None:
payload["price"] = str(price)
if sl_price is not None:
payload["sl_price"] = str(sl_price)
if tp_price is not None:
payload["tp_price"] = str(tp_price)
if trailing_pips is not None and trailing_pips > 0:
payload["trailing_pips"] = str(trailing_pips)
body = json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":")
).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "AlgoWay-Python-EMA/1.0"
}
logger.info(
"Prepared AlgoWay signal request_id=%s action=%s ticker=%s quantity=%s payload=%s",
request_id,
action,
ticker,
quantity,
body.decode("utf-8")
)
if self.config.dry_run:
logger.info("Dry-run enabled; webhook request was not sent request_id=%s", request_id)
return {
"request_id": request_id,
"dry_run": True,
"payload": payload
}
request = Request(
self.config.webhook_url,
data=body,
headers=headers,
method="POST"
)
started_at = time.monotonic()
try:
with urlopen(request, timeout=self.config.timeout_seconds) as response:
status_code = response.status
response_text = response.read().decode("utf-8", errors="replace")
except HTTPError as exc:
response_text = exc.read().decode("utf-8", errors="replace")
logger.error(
"AlgoWay rejected signal request_id=%s status=%s response=%s",
request_id,
exc.code,
response_text
)
raise RuntimeError(
f"AlgoWay HTTP error {exc.code}: {response_text}"
) from exc
except URLError as exc:
logger.error(
"AlgoWay network error request_id=%s reason=%s",
request_id,
exc.reason
)
raise RuntimeError(f"AlgoWay network error: {exc.reason}") from exc
elapsed_ms = round((time.monotonic() - started_at) * 1000, 2)
logger.info(
"AlgoWay response request_id=%s status=%s elapsed_ms=%s response=%s",
request_id,
status_code,
elapsed_ms,
response_text
)
if not 200 <= status_code < 300:
raise RuntimeError(
f"AlgoWay returned status {status_code}: {response_text}"
)
try:
parsed_response: Any = json.loads(response_text) if response_text else {}
except json.JSONDecodeError:
parsed_response = response_text
return {
"request_id": request_id,
"status_code": status_code,
"elapsed_ms": elapsed_ms,
"response": parsed_response
}
class Ema:
def __init__(self, period: int) -> None:
if period < 2:
raise ValueError("EMA period must be at least 2")
self.multiplier = 2.0 / (period + 1.0)
self.value: float | None = None
def update(self, price: float) -> float:
if self.value is None:
self.value = price
else:
self.value = ((price - self.value) * self.multiplier) + self.value
return self.value
class EmaCrossStrategy:
def __init__(
self,
client: AlgoWayClient,
ticker: str,
quantity: float,
fast_period: int = 9,
slow_period: int = 21,
stop_loss_percent: float = 0.5,
take_profit_percent: float = 1.0
) -> None:
if fast_period >= slow_period:
raise ValueError("Fast EMA period must be lower than slow EMA period")
self.client = client
self.ticker = ticker
self.quantity = quantity
self.fast_ema = Ema(fast_period)
self.slow_ema = Ema(slow_period)
self.stop_loss_percent = stop_loss_percent
self.take_profit_percent = take_profit_percent
self.position: Position = "flat"
self.previous_relation = 0
def on_close(self, close_price: float) -> None:
fast_value = self.fast_ema.update(close_price)
slow_value = self.slow_ema.update(close_price)
relation = 1 if fast_value > slow_value else -1 if fast_value < slow_value else 0
logger.info(
"EMA update ticker=%s close=%s fast=%s slow=%s relation=%s position=%s",
self.ticker,
close_price,
round(fast_value, 8),
round(slow_value, 8),
relation,
self.position
)
crossed_up = self.previous_relation == -1 and relation == 1
crossed_down = self.previous_relation == 1 and relation == -1
if crossed_up:
self._enter_long(close_price)
elif crossed_down:
self._enter_short(close_price)
if relation != 0:
self.previous_relation = relation
def _enter_long(self, price: float) -> None:
logger.info("Long crossover detected ticker=%s price=%s", self.ticker, price)
if self.position == "short":
self.client.send_signal(
ticker=self.ticker,
action="flat",
quantity=self.quantity,
price=price,
comment="python_ema:close_short"
)
sl_price = price * (1.0 - self.stop_loss_percent / 100.0)
tp_price = price * (1.0 + self.take_profit_percent / 100.0)
self.client.send_signal(
ticker=self.ticker,
action="buy",
quantity=self.quantity,
price=price,
sl_price=round(sl_price, 8),
tp_price=round(tp_price, 8),
comment="python_ema:open_long"
)
self.position = "long"
def _enter_short(self, price: float) -> None:
logger.info("Short crossover detected ticker=%s price=%s", self.ticker, price)
if self.position == "long":
self.client.send_signal(
ticker=self.ticker,
action="flat",
quantity=self.quantity,
price=price,
comment="python_ema:close_long"
)
sl_price = price * (1.0 + self.stop_loss_percent / 100.0)
tp_price = price * (1.0 - self.take_profit_percent / 100.0)
self.client.send_signal(
ticker=self.ticker,
action="sell",
quantity=self.quantity,
price=price,
sl_price=round(sl_price, 8),
tp_price=round(tp_price, 8),
comment="python_ema:open_short"
)
self.position = "short"
def env_flag(name: str, default: bool) -> bool:
raw_value = os.getenv(name)
if raw_value is None:
return default
return raw_value.strip().lower() in {"1", "true", "yes", "on"}
def main() -> None:
webhook_url = os.getenv("ALGOWAY_WEBHOOK_URL", "").strip()
if not webhook_url:
raise RuntimeError("Set ALGOWAY_WEBHOOK_URL before running the strategy")
config = AlgoWayConfig(
webhook_url=webhook_url,
platform_name=os.getenv("ALGOWAY_PLATFORM", "metatrader5").strip(),
timeout_seconds=7.0,
dry_run=env_flag("ALGOWAY_DRY_RUN", True)
)
client = AlgoWayClient(config)
strategy = EmaCrossStrategy(
client=client,
ticker=os.getenv("ALGOWAY_TICKER", "EURUSD").strip(),
quantity=float(os.getenv("ALGOWAY_QUANTITY", "0.01")),
fast_period=9,
slow_period=21,
stop_loss_percent=0.5,
take_profit_percent=1.0
)
demo_closes = [
1.1000, 1.0995, 1.0990, 1.0985, 1.0992, 1.1002,
1.1010, 1.1018, 1.1011, 1.1001, 1.0991, 1.0982
]
logger.info(
"Starting EMA example ticker=%s platform=%s dry_run=%s",
strategy.ticker,
config.platform_name,
config.dry_run
)
for close_price in demo_closes:
strategy.on_close(close_price)
logger.info("EMA example finished final_position=%s", strategy.position)
if __name__ == "__main__":
main()
ALGOWAY_WEBHOOK_URL=https://algoway.co/your-webhook-uuid
ALGOWAY_PLATFORM=metatrader5
ALGOWAY_TICKER=EURUSD
ALGOWAY_QUANTITY=0.01
ALGOWAY_DRY_RUN=true
Keep ALGOWAY_DRY_RUN=true during the first test. Change it to false only after the payload has been reviewed, the destination route is connected and the quantity has been confirmed.
The sample intentionally separates market data from execution. The EmaCrossStrategy.on_close() method accepts one completed candle close at a time. A platform can call this method from its existing historical-data loop, websocket stream, polling task or backtesting engine.
for candle in completed_candles:
strategy.on_close(float(candle["close"]))
This separation is important. AlgoWay is not used to obtain market data for the strategy. The Python environment remains responsible for obtaining candles or ticks and deciding when a candle is complete. AlgoWay begins its work after the strategy has produced a trading instruction.
The Python client can send optional risk fields after the basic entry and exit route has been tested.
| Field | Use | Example |
|---|---|---|
sl_price |
Absolute Stop Loss price calculated by the strategy. | 1.095 |
tp_price |
Absolute Take Profit price calculated by the strategy. | 1.11 |
stop_loss |
Distance-style Stop Loss value interpreted with the selected distance model. | 50 |
take_profit |
Distance-style Take Profit value interpreted with the selected distance model. | 100 |
sltp_type |
Defines whether distance values are treated as pips or percent. |
percent |
trailing_pips |
Requests trailing-stop handling when supported by the selected route. | 30 |
Use sl_price and tp_price when the strategy calculates exact market prices. Use stop_loss and take_profit for distance values. Do not place an absolute market price into a distance field.
Field support and execution details depend on the destination. Test the exact route before using normal position size.
The Python integration must log both the HTTP status and the response body. A message saying only “request failed” is not sufficient for troubleshooting.
| Result | Meaning | Required action |
|---|---|---|
2xx |
AlgoWay accepted and processed the webhook request at the HTTP layer. | Read the response body and verify the destination result in AlgoWay Webhook Logs. |
400 |
The request, JSON or required fields are invalid. | Log the exact payload and response, then correct the field names or values. |
403 |
The webhook or subscription is not permitted to process the request. | Check the AlgoWay account, subscription and webhook status. |
415 |
The request content type is not accepted as JSON. | Send serialized JSON with Content-Type: application/json. |
| Network timeout or connection error | The client did not receive a confirmed HTTP result. | Log the event and inspect AlgoWay Webhook Logs before deciding whether another order should be sent. |
An HTTP success does not replace destination verification. The broker or exchange can still reject a command because of symbol format, account state, market hours, quantity rules, margin, credentials or destination-specific restrictions. AlgoWay Webhook Logs should remain part of the operational workflow.
A trading POST request is not a harmless data lookup. If the network connection times out after AlgoWay has already received the instruction, automatically sending the same request again can create a duplicate order.
The safe rule is:
A platform can implement deduplication or a strict signal identifier only after the required behavior has been agreed and tested. Until then, do not add generic retry middleware around live trading requests.
A hosted strategy platform does not need to expose raw HTTP code to every user. It can provide a small execution adapter with a familiar method such as place_order(). Internally, the adapter maps the platform's order object to AlgoWay JSON.
class AlgoWayExecutionAdapter:
def __init__(self, client: AlgoWayClient) -> None:
self.client = client
def place_order(
self,
side: str,
symbol: str,
quantity: float,
**kwargs: Any
) -> dict[str, Any]:
normalized_side = side.strip().lower()
action_map = {
"buy": "buy",
"long": "buy",
"sell": "sell",
"short": "sell",
"close": "flat",
"flat": "flat"
}
if normalized_side not in action_map:
raise ValueError(f"Unsupported side: {side}")
return self.client.send_signal(
ticker=symbol,
action=action_map[normalized_side],
quantity=quantity,
price=kwargs.get("price"),
sl_price=kwargs.get("sl_price"),
tp_price=kwargs.get("tp_price"),
trailing_pips=kwargs.get("trailing_pips"),
comment=kwargs.get("comment", "python_platform_adapter")
)
This lets a platform preserve its normal research and backtesting interface. The execution destination changes, but the strategy does not need separate MetaTrader, cTrader, DXtrade or exchange integrations.
strategy.place_order(...)
|
| platform adapter maps the command
v
AlgoWayExecutionAdapter.place_order(...)
|
| HTTPS POST with AlgoWay JSON
v
Personal AlgoWay webhook
If the Python sandbox does not permit outbound requests, the platform can send an internal instruction to its own backend. The backend relay then sends the HTTPS POST to AlgoWay.
Sandbox strategy | Internal platform API | Platform relay | AlgoWay webhook | Execution destination
The relay option requires more platform work, but it also provides centralized secret storage, policy enforcement, rate control and audit logging.
TestMax provides a Python Algo Playground and prop firm simulation environment for developing and validating strategy logic. AlgoWay can serve as the execution layer after a strategy has been tested and the user decides to connect a live or demo destination.
The proposed workflow is:
Build Python strategy in TestMax | Validate in backtesting and prop firm simulation | Select AlgoWay execution adapter | Send signals to the user's AlgoWay webhook | Execute on the selected account
For a first implementation, TestMax would not need to create separate integrations for every AlgoWay-supported platform. It would only need to support the universal webhook adapter and provide a secure place for the user's personal webhook URL.
The initial TestMax template can use the EMA crossover strategy from this page. The strategy is intentionally simple: it proves that TestMax can produce entries and exits, that the adapter builds valid JSON, that AlgoWay receives the request and that the selected destination returns an execution result.
| Implementation area | Python platform responsibility | AlgoWay responsibility | Relative effort |
|---|---|---|---|
| Standalone template | Allow outbound HTTPS and let the user supply a secret webhook URL. | Provide the Python client, JSON reference, evaluation access and testing support. | Small on both sides. |
| Native execution adapter | Add an AlgoWay provider option, secret field and mapping from the existing order object. | Review payload mapping and support integration testing. | Small to medium for the platform; small for AlgoWay. |
| Server-side relay | Build an authenticated internal endpoint, secret storage, relay logic and operational logs. | Provide the webhook contract and validate test requests. | Medium for the platform; small for AlgoWay. |
| Broker and exchange coverage | No separate broker integration is required for the webhook layer. | Maintain the supported destination connectors and route behavior. | Handled primarily by AlgoWay. |
The exact effort depends on the platform's sandbox rules, secret-management system and existing order API. If outbound HTTPS is already available, the initial integration can remain a template-level task rather than a core platform project.
The personal webhook URL must be treated as a secret execution credential.
The Python strategy does not need the broker password or exchange API secret. Those credentials stay inside the AlgoWay destination configuration and are not included in every trading signal.
ALGOWAY_DRY_RUN=false only after confirming the webhook URL, platform name, ticker and size.An HTTP request body is bytes, not a Python dictionary. Serialize the payload with json.dumps() before sending it.
Set Content-Type: application/json. Do not send the payload as form data unless a different endpoint explicitly requires it.
The field must identify the intended AlgoWay connector. A MetaTrader route uses metatrader5; an OKX route uses okx.
Use sl_price and tp_price for exact market prices. Use stop_loss and take_profit for distance values.
The result depends on netting, hedge and destination rules. The universal example sends flat first and then the new entry so the requested lifecycle is explicit.
Always inspect the AlgoWay response and Webhook Logs. The destination may reject an otherwise valid webhook instruction.
A timeout does not prove that the order was not received. Check the logs and account before sending the same live instruction again.
The personal webhook URL must remain private. Store it as a secret and redact it from logs that are shared outside the operating team.
No. Any Python environment that can send an HTTPS POST request with JSON can connect to the AlgoWay webhook. A reusable client class is recommended for validation and logging, but it is not a separate protocol.
Use Content-Type: application/json. Accept: application/json and a clear User-Agent are useful optional headers.
Yes. The strategy can keep the same signal logic and use the corresponding AlgoWay route and platform_name. Symbol, quantity and route-specific settings must still be verified for each destination.
Yes. Use buy for long entry, sell for short entry and flat to request position closure.
Yes, when it permits outbound HTTPS requests. If external requests are blocked, the platform can provide a server-side relay.
No. It accepts completed closing prices from the surrounding platform. This keeps the data source independent from the execution adapter.
A copy-paste example must not send live orders merely because a webhook URL is present. Dry-run forces the operator to review logs and deliberately enable delivery.
No blind retry should be used for a live trading request with an unknown result. Check AlgoWay Webhook Logs and the destination account first.
Yes. A TestMax strategy can send the universal JSON directly if the Python sandbox permits outbound HTTPS, or TestMax can provide an internal relay and native AlgoWay execution adapter.
A Python strategy needs only one universal execution connection: a valid HTTPS POST request with AlgoWay JSON and Content-Type: application/json. The strategy remains responsible for data and decisions. AlgoWay validates the signal and routes it to the selected broker, prop firm, terminal or exchange.
Start with dry-run logging, test buy, sell and flat on a demo route, inspect every response in AlgoWay Webhook Logs and add advanced order fields only after the basic lifecycle works.