Edgoro
Script · MQL5

MQL5 market buy and sell with CTrade (full script)

Short answer

Create a CTrade object, set its magic number and slippage, then call trade.Buy(lots, _Symbol, ask, sl, tp) or trade.Sell(lots, _Symbol, bid, sl, tp). A true return only means the request went out: check that trade.ResultRetcode() is 10009 (TRADE_RETCODE_DONE) before you treat the position as open.

UsesCTrade::BuyCTrade::SellCTrade::ResultRetcodeSymbolInfoTickOrderCalcMargin

The code

A script with inputs: drop it on a chart and it opens one position on that chart's symbol. It sends a real order, so run it on a demo account first.

Save it as MQL5/Scripts/open-market-order-ctrade.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
#property script_show_inputs
#include <Trade/Trade.mqh>

enum ENUM_SIDE
  {
   SIDE_BUY,  // Buy
   SIDE_SELL  // Sell
  };

input ENUM_SIDE InpSide       = SIDE_BUY; // Direction
input double    InpLots       = 0.01;     // Volume, lots
input int       InpStopPoints = 300;      // Stop-loss distance, points (0 = none)
input int       InpTakePoints = 600;      // Take-profit distance, points (0 = none)
input ulong     InpMagic      = 20261112; // Magic number
input ulong     InpSlippage   = 10;       // Maximum slippage, points

// Rounds a price to the symbol's tick size
double RoundToTick(const double price)
  {
   double tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tick <= 0.0)
      return NormalizeDouble(price, _Digits);
   return NormalizeDouble(MathRound(price / tick) * tick, _Digits);
  }

void OnStart()
  {
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED))
     {
      Print("Algo trading is off: switch on the Algo Trading button and allow it in the program's settings");
      return;
     }

   CTrade trade;
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);
   trade.SetTypeFillingBySymbol(_Symbol);

   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick) || tick.bid <= 0.0)
     {
      Print("No price for ", _Symbol, " yet");
      return;
     }

   bool   buy   = (InpSide == SIDE_BUY);
   double entry = buy ? tick.ask : tick.bid;   // a buy opens at the Ask, a sell at the Bid
   double exit  = buy ? tick.bid : tick.ask;   // it closes at the other price, so stops are measured from there
   double sl    = 0.0;
   double tp    = 0.0;
   if(InpStopPoints > 0)
      sl = RoundToTick(buy ? exit - InpStopPoints * _Point : exit + InpStopPoints * _Point);
   if(InpTakePoints > 0)
      tp = RoundToTick(buy ? exit + InpTakePoints * _Point : exit - InpTakePoints * _Point);

   // Check the margin first: cheaper than a refused order
   double margin = 0.0;
   ENUM_ORDER_TYPE type = buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   if(!OrderCalcMargin(type, _Symbol, InpLots, entry, margin) || margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE))
     {
      PrintFormat("Not enough free margin for %.2f lots of %s (needs %.2f)", InpLots, _Symbol, margin);
      return;
     }

   bool sent = buy ? trade.Buy(InpLots, _Symbol, entry, sl, tp, "example")
                   : trade.Sell(InpLots, _Symbol, entry, sl, tp, "example");
   uint rc = trade.ResultRetcode();
   // true only means the request went out: the retcode says what the server did with it
   if(!sent || (rc != TRADE_RETCODE_DONE && rc != TRADE_RETCODE_DONE_PARTIAL))
     {
      PrintFormat("%s failed: retcode %u (%s)", buy ? "Buy" : "Sell", rc, trade.ResultRetcodeDescription());
      return;
     }
   PrintFormat("%s %.2f %s at %s, deal #%I64u", buy ? "Bought" : "Sold", trade.ResultVolume(), _Symbol,
               DoubleToString(trade.ResultPrice(), _Digits), trade.ResultDeal());
  }

How it works

  • TERMINAL_TRADE_ALLOWED and MQL_TRADE_ALLOWED. The first is the Algo Trading button in the toolbar, the second the "Allow Algo Trading" box in the program's own settings. With either off, the order is refused before it reaches the broker.
  • SetTypeFillingBySymbol(_Symbol). On Market Execution symbols, recent versions of CTrade choose Fill or Kill or Immediate or Cancel by themselves before sending. On Exchange Execution symbols they use the policy you set, and this call reads it from the symbol instead of hard-coding one.
  • entry and exit. A buy opens at the Ask and closes at the Bid, so its stop-loss and take-profit are checked against the Bid. Measuring them from exit keeps the full distance you asked for, whatever the spread.
  • RoundToTick(). Prices must sit on the symbol's tick grid. On most forex pairs a tick is one point, but on some indices and metals it's larger, and NormalizeDouble() alone leaves an invalid price.
  • OrderCalcMargin(). It returns the margin the order needs in the account currency. Comparing it with ACCOUNT_MARGIN_FREE before sending turns a refused order into a clear message.
  • The retcode check. Buy() and Sell() return the result of OrderSend(). The fill is confirmed by 10009, or 10010 (TRADE_RETCODE_DONE_PARTIAL) when only part of the volume was available; ResultDeal(), ResultPrice() and ResultVolume() then describe it.

Pitfalls

  • Treating true as a fill. The order can still be requoted or rejected. Logging ResultRetcode() and ResultRetcodeDescription() after every order is the habit that makes the rest of this list easy to diagnose.
  • Stops measured from the entry price. A buy's stop placed at Ask minus the distance ends up one spread closer to the Bid than you think, and close stops are refused with 10016 invalid stops.
  • A volume the symbol doesn't accept. 0.015 lots on a symbol with a 0.01 step, or less than the minimum, fails with 10014 invalid volume. Normalizing price and volume shows the rounding.
  • Algo trading switched off. The script fails with 10027 when the toolbar button is off, and with 4752 when the program's own permission is off. The check at the top catches both.
  • Netting accounts. On a netting account there is one position per symbol: a buy while a sell is open reduces, closes or reverses that sell instead of opening a second position.

Variations

  • Inside an EA. Declare the CTrade object globally, configure it once in OnInit(), and send orders from OnTick() only when a new bar brings a signal, so one signal can't open a trade on every tick.
  • Without CTrade. The same order with MqlTradeRequest and OrderSend() is in the MQL5 error library, with the two numbers to read when it fails.
  • Risk-based volume. Replace the fixed InpLots with a size computed from the stop distance and a percentage of equity: MQL5 lot size from risk percent has the function.
  • Many orders at once. trade.SetAsyncMode(true) returns without waiting for the server; the results then arrive in OnTradeTransaction(). Keep the synchronous default unless you send dozens of orders in a row.

Questions

Why does trade.Buy() return true but no position opens?

Because true only means the request was sent and checked. Read trade.ResultRetcode(): anything other than 10009 or 10010 means the server didn't fill it, and ResultRetcodeDescription() says why. On a netting account, also check whether the buy simply closed an open sell.

Should I pass the price to trade.Buy() or leave it at 0?

Either works. With 0, CTrade fills in the current Ask (the Bid for a sell) itself. On Market Execution symbols the server fills at the market price anyway; the price you pass matters on Instant Execution symbols, where it is the reference for the allowed slippage.

What is the difference between CTrade and OrderSend()?

CTrade is the standard library's wrapper around OrderSend(): it fills in the request structure, picks a filling policy and keeps the result for you. The server applies exactly the same rules to both, so the same retcodes come back.

Can a script open a trade on another symbol than the chart's?

Yes: pass the symbol's name instead of _Symbol and use that symbol's own Bid, Ask, digits and tick size. The symbol must be in Market Watch; SymbolSelect(name, true) adds it.

This program is a minimal example written to show one technique, not a trading system, and none of it is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

All MQL5 errors

Want it inside a complete EA?

Edgoro's MT5 EA generator writes complete Expert Advisors with this order handling built in: stops from the right price, volumes within the broker's limits and every retcode logged. A clean build proves the code compiles, not that a strategy works: test it on a demo account.