+ +

Trading Module

+ + +
+ + + +

+ mt5cli.trading + + +

+ +
+ +

Trading-capable MetaTrader 5 session helpers and operational utilities.

+ + + + + + + + + + +
+ + + + + + + +
+ + + +

+ OrderSide + + + + module-attribute + + +

+
OrderSide = Literal['long', 'short']
+
+ +
+ +
+ +
+ +
+ + + +

+ PositionSide + + + + module-attribute + + +

+
PositionSide = Literal['long', 'short']
+
+ +
+ +
+ +
+ +
+ + + +

+ __all__ + + + + module-attribute + + +

+
__all__ = [
+    "OrderSide",
+    "PositionSide",
+    "calculate_margin_and_volume",
+    "detect_position_side",
+    "determine_order_limits",
+    "mt5_trading_session",
+]
+
+ +
+ +
+ +
+ + + + +
+ + +

+ calculate_margin_and_volume + + +

+
calculate_margin_and_volume(
+    client: Mt5TradingClient,
+    symbol: str,
+    unit_margin_ratio: float,
+    preserved_margin_ratio: float,
+) -> dict[str, float]
+
+ +
+ +

Calculate tradable margin and volumes from account free margin.

+

Applies preserved_margin_ratio to keep a reserve off margin_free, +then allocates unit_margin_ratio of the remainder as the margin budget +for volume sizing on both buy and sell sides.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ client + + Mt5TradingClient + +
+

Connected Mt5TradingClient instance.

+
+
+ required +
+ symbol + + str + +
+

Symbol used for minimum-lot margin and volume calculations.

+
+
+ required +
+ unit_margin_ratio + + float + +
+

Fraction of post-reserve margin to allocate per unit.

+
+
+ required +
+ preserved_margin_ratio + + float + +
+

Fraction of margin_free to preserve.

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ dict[str, float] + +
+

Dictionary with margin_free, available_margin, trade_margin,

+
+
+ dict[str, float] + +
+

buy_volume, and sell_volume. Negative margin_free values are

+
+
+ dict[str, float] + +
+

clamped to 0.0 before sizing.

+
+
+ + +
+ Source code in mt5cli/trading.py +
def calculate_margin_and_volume(
+    client: Mt5TradingClient,
+    symbol: str,
+    unit_margin_ratio: float,
+    preserved_margin_ratio: float,
+) -> dict[str, float]:
+    """Calculate tradable margin and volumes from account free margin.
+
+    Applies ``preserved_margin_ratio`` to keep a reserve off ``margin_free``,
+    then allocates ``unit_margin_ratio`` of the remainder as the margin budget
+    for volume sizing on both buy and sell sides.
+
+    Args:
+        client: Connected ``Mt5TradingClient`` instance.
+        symbol: Symbol used for minimum-lot margin and volume calculations.
+        unit_margin_ratio: Fraction of post-reserve margin to allocate per unit.
+        preserved_margin_ratio: Fraction of ``margin_free`` to preserve.
+
+    Returns:
+        Dictionary with ``margin_free``, ``available_margin``, ``trade_margin``,
+        ``buy_volume``, and ``sell_volume``. Negative ``margin_free`` values are
+        clamped to ``0.0`` before sizing.
+    """
+    _require_unit_ratio(unit_margin_ratio, "unit_margin_ratio")
+    _require_unit_ratio(preserved_margin_ratio, "preserved_margin_ratio")
+
+    account = client.account_info_as_dict()
+    margin_free = max(0.0, float(account.get("margin_free") or 0.0))
+    available_margin = margin_free * (1.0 - preserved_margin_ratio)
+    trade_margin = available_margin * unit_margin_ratio
+    buy_volume = client.calculate_volume_by_margin(symbol, trade_margin, "BUY")
+    sell_volume = client.calculate_volume_by_margin(symbol, trade_margin, "SELL")
+    return {
+        "margin_free": margin_free,
+        "available_margin": available_margin,
+        "trade_margin": trade_margin,
+        "buy_volume": buy_volume,
+        "sell_volume": sell_volume,
+    }
+
+
+
+ +
+ +
+ + +

+ detect_position_side + + +

+
detect_position_side(
+    client: Mt5TradingClient, symbol: str
+) -> PositionSide | None
+
+ +
+ +

Detect the net open position side for a symbol.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ client + + Mt5TradingClient + +
+

Connected Mt5TradingClient instance.

+
+
+ required +
+ symbol + + str + +
+

Symbol to inspect.

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ PositionSide | None + +
+

"long" when net buy volume exceeds sell volume, "short" when

+
+
+ PositionSide | None + +
+

net sell volume exceeds buy volume, or None when no positions exist

+
+
+ PositionSide | None + +
+

or buy/sell volumes are exactly balanced.

+
+
+ + +
+ Source code in mt5cli/trading.py +
61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
def detect_position_side(
+    client: Mt5TradingClient,
+    symbol: str,
+) -> PositionSide | None:
+    """Detect the net open position side for a symbol.
+
+    Args:
+        client: Connected ``Mt5TradingClient`` instance.
+        symbol: Symbol to inspect.
+
+    Returns:
+        ``"long"`` when net buy volume exceeds sell volume, ``"short"`` when
+        net sell volume exceeds buy volume, or ``None`` when no positions exist
+        or buy/sell volumes are exactly balanced.
+    """
+    positions = client.positions_get_as_df(symbol=symbol)
+    if positions.empty:
+        return None
+
+    buy_type = client.mt5.POSITION_TYPE_BUY
+    sell_type = client.mt5.POSITION_TYPE_SELL
+    buy_volume = _sum_position_volume(positions, buy_type)
+    sell_volume = _sum_position_volume(positions, sell_type)
+    net_volume = buy_volume - sell_volume
+    if net_volume > 0:
+        return "long"
+    if net_volume < 0:
+        return "short"
+    return None
+
+
+
+ +
+ +
+ + +

+ determine_order_limits + + +

+
determine_order_limits(
+    client: Mt5TradingClient,
+    symbol: str,
+    side: OrderSide | str,
+    stop_loss_limit_ratio: float,
+    take_profit_limit_ratio: float,
+) -> dict[str, float | None]
+
+ +
+ +

Derive entry and protective order prices from current market quotes.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ client + + Mt5TradingClient + +
+

Connected Mt5TradingClient instance.

+
+
+ required +
+ symbol + + str + +
+

Symbol used for the quote lookup.

+
+
+ required +
+ side + + OrderSide | str + +
+

Position side as "long"/"short" ("buy"/"sell" +aliases are accepted).

+
+
+ required +
+ stop_loss_limit_ratio + + float + +
+

Relative distance from entry for stop loss in +[0, 1). A value of 0 omits the stop loss.

+
+
+ required +
+ take_profit_limit_ratio + + float + +
+

Relative distance from entry for take profit in +[0, 1). A value of 0 omits the take profit.

+
+
+ required +
+ + +

Returns:

+ + + + + + + + + + + + + + + + + +
TypeDescription
+ dict[str, float | None] + +
+

Dictionary with entry, stop_loss, and take_profit keys.

+
+
+ dict[str, float | None] + +
+

Omitted protective levels are returned as None.

+
+
+ + +
+ Source code in mt5cli/trading.py +
def determine_order_limits(
+    client: Mt5TradingClient,
+    symbol: str,
+    side: OrderSide | str,
+    stop_loss_limit_ratio: float,
+    take_profit_limit_ratio: float,
+) -> dict[str, float | None]:
+    """Derive entry and protective order prices from current market quotes.
+
+    Args:
+        client: Connected ``Mt5TradingClient`` instance.
+        symbol: Symbol used for the quote lookup.
+        side: Position side as ``"long"``/``"short"`` (``"buy"``/``"sell"``
+            aliases are accepted).
+        stop_loss_limit_ratio: Relative distance from entry for stop loss in
+            ``[0, 1)``. A value of ``0`` omits the stop loss.
+        take_profit_limit_ratio: Relative distance from entry for take profit in
+            ``[0, 1)``. A value of ``0`` omits the take profit.
+
+    Returns:
+        Dictionary with ``entry``, ``stop_loss``, and ``take_profit`` keys.
+        Omitted protective levels are returned as ``None``.
+    """
+    _require_protective_ratio(stop_loss_limit_ratio, "stop_loss_limit_ratio")
+    _require_protective_ratio(take_profit_limit_ratio, "take_profit_limit_ratio")
+    normalized_side = _normalize_order_side(side)
+    tick = client.symbol_info_tick_as_dict(symbol=symbol)
+    entry = float(tick["ask"] if normalized_side == "long" else tick["bid"])
+
+    stop_loss: float | None = None
+    if stop_loss_limit_ratio > 0:
+        if normalized_side == "long":
+            stop_loss = entry * (1.0 - stop_loss_limit_ratio)
+        else:
+            stop_loss = entry * (1.0 + stop_loss_limit_ratio)
+
+    take_profit: float | None = None
+    if take_profit_limit_ratio > 0:
+        if normalized_side == "long":
+            take_profit = entry * (1.0 + take_profit_limit_ratio)
+        else:
+            take_profit = entry * (1.0 - take_profit_limit_ratio)
+
+    return {
+        "entry": entry,
+        "stop_loss": stop_loss,
+        "take_profit": take_profit,
+    }
+
+
+
+ +
+ +
+ + +

+ mt5_trading_session + + +

+
mt5_trading_session(
+    config: Mt5Config | None = None, retry_count: int = 0
+) -> Iterator[Mt5TradingClient]
+
+ +
+ +

Open a trading-capable MT5 session and always shut down safely.

+

Launches the MetaTrader 5 terminal using Mt5Config.path when set, +initializes and logs in via initialize_and_login_mt5(), yields a +connected :class:~pdmt5.Mt5TradingClient, and calls shutdown() on +exit even when an error is raised inside the context.

+ + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
+ config + + Mt5Config | None + +
+

MT5 connection configuration. Defaults to an empty config that +attaches to a running terminal.

+
+
+ None +
+ retry_count + + int + +
+

Number of initialization retries passed to +Mt5TradingClient.

+
+
+ 0 +
+ + +

Yields:

+ + + + + + + + + + + + + +
TypeDescription
+ Mt5TradingClient + +
+

Connected Mt5TradingClient bound to the session.

+
+
+ + +
+ Source code in mt5cli/trading.py +
@contextmanager
+def mt5_trading_session(
+    config: Mt5Config | None = None,
+    retry_count: int = 0,
+) -> Iterator[Mt5TradingClient]:
+    """Open a trading-capable MT5 session and always shut down safely.
+
+    Launches the MetaTrader 5 terminal using ``Mt5Config.path`` when set,
+    initializes and logs in via ``initialize_and_login_mt5()``, yields a
+    connected :class:`~pdmt5.Mt5TradingClient`, and calls ``shutdown()`` on
+    exit even when an error is raised inside the context.
+
+    Args:
+        config: MT5 connection configuration. Defaults to an empty config that
+            attaches to a running terminal.
+        retry_count: Number of initialization retries passed to
+            ``Mt5TradingClient``.
+
+    Yields:
+        Connected ``Mt5TradingClient`` bound to the session.
+    """
+    mt5_config = config or build_config()
+    client = Mt5TradingClient(config=mt5_config, retry_count=retry_count)
+    try:
+        client.initialize_and_login_mt5()
+        yield client
+    finally:
+        client.shutdown()
+
+
+
+ +
+ + + +
+ +
+ +

Trading-capable MT5 sessions

+

mt5_trading_session() complements the read-only mt5_session() helper in +sdk.py. It yields a connected pdmt5.Mt5TradingClient, uses +Mt5Config.path to launch the terminal when configured, and always calls +shutdown() on exit.

+
from pdmt5 import Mt5Config
+
+from mt5cli import mt5_trading_session
+
+with mt5_trading_session(
+    Mt5Config(path=r"C:\Program Files\MetaTrader 5\terminal64.exe", login=12345),
+    retry_count=2,
+) as client:
+    positions = client.positions_get_as_df(symbol="EURUSD")
+
+

The read-only Mt5CliClient / mt5_session() API is unchanged.

+

Operational trading helpers

+

These helpers are strategy-agnostic and do not depend on signal detection, +betting logic, or scheduling code in downstream applications.

+
from mt5cli import (
+    calculate_margin_and_volume,
+    detect_position_side,
+    determine_order_limits,
+)
+
+side = detect_position_side(client, "EURUSD")
+sizing = calculate_margin_and_volume(
+    client,
+    "EURUSD",
+    unit_margin_ratio=0.5,
+    preserved_margin_ratio=0.2,
+)
+limits = determine_order_limits(
+    client,
+    "EURUSD",
+    side="long",
+    stop_loss_limit_ratio=0.01,
+    take_profit_limit_ratio=0.02,
+)
+
+

Protective ratios must satisfy 0 <= ratio < 1; 0 omits that level. +calculate_margin_and_volume() clamps negative margin_free to 0.0 +before sizing.

+

Migration from mteor-local helpers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
mteor-local concernmt5cli replacement
Manual terminal spawn/kill around trading codemt5_trading_session()
Local position-side detectiondetect_position_side()
Local margin/volume sizingcalculate_margin_and_volume()
Local SL/TP price derivationdetermine_order_limits()
Throttled SQLite history loop with ad-hoc error handlingThrottledHistoryUpdater(suppress_errors=True)
+

Keep read-only data collection on mt5_session() / Mt5CliClient; use +mt5_trading_session() only where order placement or trading calculations are +required.