Python client library for TradeLink - a local app that executes trades directly against MetaTrader 5. Use this instead of building raw HTTP requests by hand.
Not on PyPI yet (schema is still evolving across the TradeLink ecosystem - see the server repo's notes on why). Install directly from GitHub:
pip install git+https://github.com/symoeez/tradelink-python.git
Or for local development, editable:
git clone https://github.com/symoeez/tradelink-python.git
cd tradelink-python
pip install -e .
from tradelink import TradeLinkClient, TradeLinkError
client = TradeLinkClient(base_url="http://your-vps-ip/webhook", secret="your-secret")
try:
resp = client.open_long("EURUSD", volume=0.01, sl=1.0820, tp=1.0950)
if resp.status == "success":
print("Opened, ticket:", resp.ticket)
else:
print("Rejected:", resp.message)
except TradeLinkError as e:
print("Couldn't reach TradeLink:", e)See quickstart.py for a runnable end-to-end example, and
scripts/verify_library.py for a full smoke test exercising every method.
Both scripts need real credentials - edit BASE_URL and SECRET directly
at the top of the file before running (no .env file needed):
quickstart.py - minimal getting-started example. Read this first if
you're new to the library. One trade opened and closed with default
settings.
python quickstart.py
scripts/verify_library.py - comprehensive smoke test (~40 checks)
covering magic number defaults/overrides, close-by-ticket, cancel_order,
modify_position, isolation between different magic numbers, and all data
queries. A maintainer tool for confirming nothing broke after a change, not
a getting-started example - places more real trades than quickstart does.
python scripts/verify_library.py
Not part of the automated test suite (see below) since it needs a live server and places real trades - never run automatically in CI.
TradeLinkError(raised) - the request never completed: connection refused, timeout, wrong secret, server error, or invalid parameters caught before a network call was even made. These are bugs or config problems in your code, not normal trading outcomes.TradeResponsewithstatus="error"(returned, not raised) - the request reached MT5 but the trade/query itself failed: broker rejected the order, no matching position to close, invalid symbol. Branch onresponse.statusfor these; they're expected outcomes your strategy code should handle.
client.open_long(symbol, volume, type="market", price=None, sl=None, tp=None, magic=None, comment=None)
client.open_short(symbol, volume, type="market", price=None, sl=None, tp=None, magic=None, comment=None)type:"market","limit", or"stop"-priceis required when not"market".sl/tp: optional, omit for no stop loss / take profit.magic: optional. If not given, defaults to990011server-side.magic=0is reserved for manual trades placed by hand in the terminal - never pass it unless you deliberately want to interact with manually-placed trades.comment: optional, defaults to"tradelink"server-side.
# Simple market open
client.open_long("EURUSD", volume=0.01)
# With SL/TP
client.open_long("EURUSD", volume=0.01, sl=1.0820, tp=1.0950)
# Tagged with your own magic number and comment, for strategy isolation
client.open_short("EURUSD", volume=0.01, magic=555555, comment="my_strategy_v1")
# Pending limit order
client.open_long("EURUSD", volume=0.01, type="limit", price=1.0700)client.close_long(symbol, magic=None, ticket=None)
client.close_short(symbol, magic=None, ticket=None)- No
magicgiven → closes positions with the default magic (990011). - Explicit
magic→ only closes positions tagged with that exact magic. - Explicit
magic=0→ only closes manual (magic-less) positions. ticket→ closes that exact position, ignoring magic entirely.
client.close_long("EURUSD") # closes default-magic longs
client.close_long("EURUSD", magic=555555) # closes only that strategy's longs
client.close_long("EURUSD", magic=0) # closes only manual longs
client.close_long("EURUSD", ticket=57538438692) # closes that exact positionclient.cancel_order(ticket=None, symbol=None, side=None, magic=None)Same pattern as closing positions:
ticketgiven → cancels exactly that order, ignoringside/magicentirely.symbolis optional but recommended as a sanity check - if given, the ticket must belong to that symbol.- No
ticket→symbolandside("long_open"or"short_open") are required. Cancels every matching pending order - there can be more than one (e.g. a limit and a stop at different prices).magicdefaults to990011if omitted, same as opening/closing.
Only affects pending limit/stop orders, not open positions - use close_long/close_short for those.
client.cancel_order(ticket=57538438692, symbol="EURUSD") # cancel one specific order
client.cancel_order(symbol="EURUSD", side="long_open") # cancel all default-magic pending longs
client.cancel_order(symbol="EURUSD", side="long_open", magic=12345) # cancel all of that strategy's pending longsclient.modify_position(ticket=None, symbol=None, side=None, magic=None, sl=None, tp=None)Changes SL and/or TP on an already-open position, in place - distinct from opening (which sets SL/TP once, at creation) or closing (which exits the position). Same ticket-or-symbol+side+magic pattern as everywhere else:
ticketgiven → modifies exactly that position, ignoringside/magic.- No
ticket→symbolandsiderequired,magicoptional (defaults to990011). Modifies every matching position.
Send only the field you want to change - the other is preserved automatically, never wiped. Sending neither sl nor tp is rejected (nothing to modify).
client.modify_position(ticket=57538438692, sl=1.0850) # change SL only, TP untouched
client.modify_position(ticket=57538438692, tp=1.1200) # change TP only, SL untouched
client.modify_position(symbol="EURUSD", side="long_open", sl=1.0850) # bulk, default magicclient.move_to_breakeven(ticket=None, symbol=None, side=None, magic=None)Sets a position's SL to its own entry price. Same ticket-or-symbol+side+magic pattern:
ticketgiven → moves exactly that position, regardless of its current profit or loss - naming an exact ticket is treated as a deliberate choice.- No
ticket→symbolandsiderequired,magicoptional (defaults to990011). Only positions currently in profit are moved - this is a fixed rule, not a flag, since moving a losing position's SL to its own entry price has no sensible use case in the bulk case.
TP is always left untouched - this only ever changes SL.
client.move_to_breakeven(ticket=57538438692) # one specific position, any profit state
client.move_to_breakeven(symbol="EURUSD", side="long_open") # all profitable default-magic longs
client.move_to_breakeven(symbol="EURUSD", side="long_open", magic=12345) # scoped to one strategyclient.get_candles(symbol, timeframe="H1", count=100)
client.get_positions(symbol=None) # omit symbol for all open positions
client.get_orders(symbol=None) # pending limit/stop orders
client.get_history(days=7, symbol=None)
client.get_account_info()All return a TradeResponse - check .status, use .data for the payload.
positions = client.get_positions(symbol="EURUSD")
for p in positions.data:
print(p["ticket"], p["type"], p["volume"], p["magic"], p["comment"])Ticket vs. order ticket, in get_history results: MT5 has two
separate ticket spaces. open_long()/open_short() return an order
ticket. get_history() results include both ticket (a deal ticket -
a different number) and order (the order ticket) - match against
order if you're correlating history against a ticket an open call gave
you.
- TradeLink - the server app this library talks to. Handles the actual MT5 connection, TradingView webhook intake, and trade execution. You need this running before this client can do anything.