sendOrder method

Future<BinanceTradeResponse> sendOrder({
  1. String baseUri = defaultUri,
  2. required String symbol,
  3. Side side = Side.buy,
  4. OrderType type = OrderType.limit,
  5. TimeInForce timeInForce = TimeInForce.gtc,
  6. double? quantity,
  7. double? quoteOrderQty,
  8. double? price,
  9. String? newClientOrderId,
  10. double? stopPrice,
  11. double? icebergQty,
  12. OrderResponseType? newOrderRespType,
  13. int recvWindow = 5000,
  14. bool dryRun = false,
})

Will send a trade order to the matching engine.

API Key required : yes (+ signature)

Query weight : 1

Returns a BinanceTradeResponse based on the given OrderResponseType.

Throws a BinanceApiError if an error occurs.

If dryRun is true, it will send a test trade order to the Binance API but will not be sent to the matching engine.

This mode shall be used to :

  • validate your keys (if invalid, will throw BinanceApiError(401, Unauthorized))
  • check server availability (prefer using ping for this)
  • validate the timing security (see below doc)
  • validate that you are using the right set of parameters (if invalid: client throws ArgumentError, server may throw BinanceApiError(400, Bad Request))

Other info from docs :

Any LIMIT or LIMIT_MAKER type order can be made an iceberg order by sending an icebergQty. Any order with an icebergQty MUST have timeInForce set to GTC. (it is done by library) MARKET orders using quoteOrderQty will not break LOT_SIZE filter rules; the order will execute a quantity that will have the notional value as close as possible to quoteOrderQty. Trigger order price rules against market price for both MARKET and LIMIT versions: Price above market price: STOP_LOSS BUY, TAKE_PROFIT SELL Price below market price: STOP_LOSS SELL, TAKE_PROFIT BUY

Timing security :

A SIGNED endpoint also requires a parameter, timestamp, to be sent which should be the millisecond timestamp of when the request was created and sent. An additional parameter, recvWindow, may be sent to specify the number of milliseconds after timestamp the request is valid for. If recvWindow is not sent, it defaults to 5000. The logic is as follows:

 if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
   // process request
 } else {
   // reject request
 }

Serious trading is about timing. Networks can be unstable and unreliable, which can lead to requests taking varying amounts of time to reach the servers. With recvWindow, you can specify that the request must be processed within a certain number of milliseconds or be rejected by the server.

It is recommended to use a small recvWindow of 5000 or less! The max cannot go beyond 60,000!

Implementation

Future<BinanceTradeResponse> sendOrder({
  String baseUri = defaultUri,
  required String symbol,
  Side side = Side.buy,
  OrderType type = OrderType.limit,
  TimeInForce timeInForce = TimeInForce.gtc,
  double? quantity,
  double? quoteOrderQty,
  double? price,

  /// A unique id among open orders. Automatically generated if not sent.
  /// Orders with the same newClientOrderID can be accepted only when the previous one is filled,
  /// otherwise the order will be rejected.
  String? newClientOrderId,

  /// Used with `STOP_LOSS`, `STOP_LOSS_LIMIT`, `TAKE_PROFIT`, and `TAKE_PROFIT_LIMIT` orders.
  double? stopPrice,

  /// Used with `LIMIT`, `STOP_LOSS_LIMIT`, and `TAKE_PROFIT_LIMIT to create an iceberg order.
  double? icebergQty,

  /// Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.
  OrderResponseType? newOrderRespType,

  /// The value cannot be greater than 60000
  int recvWindow = 5000,
  bool dryRun = false,
}) async {
  if (recvWindow < kMinRecvWindow || recvWindow > kMaxRecvWindow) {
    throw RangeError(
        'recvWindow should be a positive value less than $kMaxRecvWindow');
  }
  // additional mandatory parameters
  switch (type) {
    case OrderType.limit:
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (price == null) throw ArgumentError.notNull('price');
      break;
    case OrderType.market:
      // MARKET orders using the quantity field specifies the amount of the base asset the user wants to buy or sell at the market price.
      // E.g. MARKET order on BTCUSDT will specify how much BTC the user is buying or selling.
      //
      // MARKET orders using quoteOrderQty specifies the amount the user wants to spend (when buying) or receive (when selling)
      // the quote asset; the correct quantity will be determined based on the market liquidity and quoteOrderQty.
      //
      // E.g. Using the symbol BTCUSDT:
      // BUY side, the order will buy as many BTC as quoteOrderQty USDT can.
      // SELL side, the order will sell as much BTC needed to receive quoteOrderQty USDT.
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (quoteOrderQty == null) throw ArgumentError.notNull('quoteOrderQty');
      break;
    case OrderType.stopLoss:
      // This will execute a MARKET order when the stopPrice is reached.
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (stopPrice == null) throw ArgumentError.notNull('stopPrice');
      break;
    case OrderType.stopLossLimit:
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (price == null) throw ArgumentError.notNull('price');
      if (stopPrice == null) throw ArgumentError.notNull('stopPrice');
      break;
    case OrderType.takeProfit:
      // This will execute a MARKET order when the stopPrice is reached.
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (stopPrice == null) throw ArgumentError.notNull('stopPrice');
      break;
    case OrderType.takeProfitLimit:
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (price == null) throw ArgumentError.notNull('price');
      if (stopPrice == null) throw ArgumentError.notNull('stopPrice');
      break;
    case OrderType.limitMaker:
      // This is a LIMIT order that will be rejected if the order immediately matches and trades as a taker.
      // This is also known as a POST-ONLY order.
      if (quantity == null) throw ArgumentError.notNull('quantity');
      if (price == null) throw ArgumentError.notNull('price');
      break;
  }
  // build params
  final queryParameters = buildTradeOrderParams(
    symbol,
    side,
    type,
    quantity,
    recvWindow,
    timeInForce,
    quoteOrderQty,
    price,
    newClientOrderId,
    stopPrice,
    icebergQty,
    newOrderRespType,
  );
  // send request
  final result = await sendRequest(
    baseUri,
    dryRun ? testTradeOrderPath : tradeOrderPath,
    queryParameters: queryParameters,
    requestMethod: RequestMethod.post,
    withSignature: true,
  );
  if (dryRun) {
    // API response is empty Map, so here we just build an empty [BinanceTradeResponse]
    return BinanceTradeResponse.dry(symbol);
  } else {
    return BinanceTradeResponse.fromJson(result);
  }
}