Skip to content

Standardize isPostOnly #651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti
// DONE
protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
// In Aquanow market order, when buying crypto the amount of crypto that is bought is the receiveQuantity
// and when selling the amount of crypto that is sold is the deliverQuantity
string amountParameter = order.IsBuy ? "receiveQuantity" : "deliverQuantity";
Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/API/Exchanges/BL3P/ExchangeBL3PAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ private string GetSignKey(IHttpWebRequest request, string formData)

protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
var roundedAmount = order.RoundAmount();
var amountInt = converterToEight.FromDecimal(roundedAmount);

Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/API/Exchanges/BTSE/ExchangeBTSEAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
dict.Add("size", request.Amount);
dict.Add("side", request.IsBuy ? "BUY" : "SELL");
dict.Add("symbol", request.MarketSymbol);
if (request.IsPostOnly != null) payload["postOnly"] = request.IsPostOnly;

switch (request.OrderType )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,11 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
payload["side"] = order.IsBuy ? "BUY" : "SELL";
if (order.OrderType == OrderType.Stop)
payload["type"] = "STOP_LOSS";//if order type is stop loss/limit, then binance expect word 'STOP_LOSS' inestead of 'STOP'
else if (order.IsPostOnly == true)
{
if (order.OrderType == OrderType.Limit) payload["type"] = "LIMIT_MAKER"; // LIMIT_MAKER are LIMIT orders that will be rejected if they would immediately match and trade as a taker.
else throw new NotImplementedException("PostOnly with non limit orders are not currently supported on Binance. Please submit a PR if you are interested in this feature");
}
else
payload["type"] = order.OrderType.ToStringUpperInvariant();

Expand Down
2 changes: 2 additions & 0 deletions src/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
if (order.OrderType == OrderType.Stop)
throw new InvalidOperationException("Bitbank does not support stop order");
Dictionary<string, object> payload = await GetNoncePayloadAsync();
if (order.IsPostOnly != null)
payload["post_only"] = order.IsPostOnly;
payload.Add("pair", NormalizeMarketSymbol(order.MarketSymbol));
payload.Add("amount", order.Amount.ToStringInvariant());
payload.Add("side", order.IsBuy ? "buy" : "sell");
Expand Down
8 changes: 4 additions & 4 deletions src/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -693,10 +693,10 @@ private void AddOrderToPayload(ExchangeOrderRequest order, Dictionary<string, ob
if(order.OrderType!=OrderType.Market)
payload["price"] = order.Price;

if (order.ExtraParameters.TryGetValue("execInst", out var execInst))
{
payload["execInst"] = execInst;
}
if (order.IsPostOnly == true)
payload["execInst"] = "ParticipateDoNotInitiate"; // Also known as a Post-Only order. If this order would have executed on placement, it will cancel instead. This is intended to protect you from the far touch moving towards you while the order is in transit. It is not intended for speculating on the far touch moving away after submission - we consider such behaviour abusive and monitor for it.

order.ExtraParameters.CopyTo(payload);
}

private ExchangePosition ParsePosition(JToken token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
{
payload["price"] = "1";
}
if (order.IsPostOnly == true) payload["flags"] = "4096"; // The post-only limit order option ensures the limit order will be added to the order book and not match with a pre-existing order unless the pre-existing order is a hidden order.
order.ExtraParameters.CopyTo(payload);
JToken obj = await MakeJsonRequestAsync<JToken>("/order/new", BaseUrlV1, payload);
return ParseOrder(obj);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ private static Dictionary<string, decimal> ExtractDictionary(JObject responseObj

protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
string action = order.IsBuy ? "buy" : "sell";
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
string action = order.IsBuy ? "buy" : "sell";
string market = order.OrderType == OrderType.Market ? "/market" : "";
string url = $"/{action}{market}/{order.MarketSymbol}/";
Dictionary<string, object> payload = await GetNoncePayloadAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
if (order.OrderType == ExchangeSharp.OrderType.Limit)
{
orderParams.Add("limit", orderPrice);
orderParams.Add("timeInForce", "GOOD_TIL_CANCELLED");
if (order.IsPostOnly == true) orderParams.Add("timeInForce", "POST_ONLY_GOOD_TIL_CANCELLED"); // This option allows market makers to ensure that their orders are making it to the order book instead of matching with a pre-existing order. Note: If the order is not a maker order, you will return an error and the order will be cancelled
else orderParams.Add("timeInForce", "GOOD_TIL_CANCELLED");
}

foreach (KeyValuePair<string, object> kv in order.ExtraParameters)
Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/API/Exchanges/Bybit/ExchangeBybitAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd

public async Task<ExchangeOrderResult> OnAmendOrderAsync(ExchangeOrderRequest order)
{
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
var payload = new Dictionary<string, object>();
payload["symbol"] = order.MarketSymbol;
if(order.OrderId != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
switch (order.OrderType)
{
case OrderType.Limit:
// set payload["post_only"] to true for default scenario when order.ExtraParameters["post_only"] is not specified
// to place non-post-only limit order one can set and pass order.ExtraParameters["post_only"]="false"
payload["post_only"] = order.ExtraParameters.TryGetValueOrDefault("post_only", "true");
if (order.IsPostOnly != null) payload["post_only"] = order.IsPostOnly; // [optional]** Post only flag, ** Invalid when time_in_force is IOC or FOK
if (order.Price == null) throw new ArgumentNullException(nameof(order.Price));
payload["price"] = order.Price.ToStringInvariant();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
payload["type"] = GetOrderType(order);
payload["price"] = order.Price;
payload["amount"] = order.Amount;
if (order.IsPostOnly != null) payload["post_only"] = order.IsPostOnly.Value ? "1" : "0"; // Default 0, enabled by 1, if enabled the order will be cancelled if it can be executed immediately, making sure there will be no market taking
var market = order.IsMargin ? "margin" : "spot";
JToken token = await MakeJsonRequestAsync<JToken>($"/{market}/order/new", payload: payload, requestMethod: "POST");
return new ExchangeOrderResult { OrderId = token["order_id"].ToStringInvariant() };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ private void AddOrderToPayload(ExchangeOrderRequest order, Dictionary<string, ob
payload.Add("side", order.IsBuy ? "buy" : "sell");
payload.Add("amount", order.Amount.ToStringInvariant());
payload.Add("price", order.Price);
if (order.IsPostOnly == true) payload["time_in_force"] += "poc"; // PendingOrCancelled, makes a post-only order that always enjoys a maker fee
}

private ExchangeOrderResult ParseOrder(JToken order)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
{ "side", (order.IsBuy ? "buy" : "sell") },
{ "type", "exchange limit" }
};
if (order.IsPostOnly == true) payload["options"] = "[maker-or-cancel]"; // This order will only add liquidity to the order book. If any part of the order could be filled immediately, the whole order will instead be canceled before any execution occurs. If that happens, the response back from the API will indicate that the order has already been canceled("is_cancelled": true in JSON). Note: some other exchanges call this option "post-only".
order.ExtraParameters.CopyTo(payload);
JToken obj = await MakeJsonRequestAsync<JToken>("/order/new", null, payload);
return ParseOrder(obj);
Expand Down
3 changes: 2 additions & 1 deletion src/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
payload["price"] = order.Price;
payload["timeInForce"] = "GTC";
}
order.ExtraParameters.CopyTo(payload);
if (order.IsPostOnly != null) payload["post_only"] = order.IsPostOnly; // Optional. If your post-only order causes a match with a pre-existing order as a taker, then the order will be cancelled.
order.ExtraParameters.CopyTo(payload);

// { "id": 0,"clientOrderId": "d8574207d9e3b16a4a5511753eeef175","symbol": "ETHBTC","side": "sell","status": "new","type": "limit","timeInForce": "GTC","quantity": "0.063","price": "0.046016","cumQuantity": "0.000","createdAt": "2017-05-15T17:01:05.092Z","updatedAt": "2017-05-15T17:01:05.092Z" }
JToken token = await MakeJsonRequestAsync<JToken>("/order", null, payload, "POST");
Expand Down
4 changes: 3 additions & 1 deletion src/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,9 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
payload["price"] = outputPrice.ToStringInvariant();
}

order.ExtraParameters.CopyTo(payload);
if (order.IsPostOnly == true) payload["timeInForce"] += "boc"; // timeInForce enum values: gtc - good till cancel,boc - book or cancel (also called as post only, or book only), ioc - immediate or cancel, fok - fill or kill

order.ExtraParameters.CopyTo(payload);

JToken obj = await MakeJsonRequestAsync<JToken>("/order/orders/place", PrivateUrlV1, payload, "POST");
order.Amount = outputQuantity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
if (order.Price == null) throw new ArgumentNullException(nameof(order.Price));
payload.Add("price", Math.Round(order.Price.Value, precision).ToStringInvariant());
}
if (order.IsPostOnly == true) payload["oflags"] = "post"; // post-only order (available when ordertype = limit)
order.ExtraParameters.CopyTo(payload);

JToken token = await MakeJsonRequestAsync<JToken>("/0/private/AddOrder", null, payload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
{
payload["type"] = "limit";
payload["price"] = order.Price.ToStringInvariant();
if (order.IsPostOnly != null) payload["postOnly"] = order.IsPostOnly; // [Optional] Post only flag, invalid when timeInForce is IOC or FOK
}
order.ExtraParameters.CopyTo(payload);

Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/API/Exchanges/LBank/ExchangeLBankAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ protected override async Task<Dictionary<string, decimal>> OnGetAmountsAsync()
//PlaceOrder 9
protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
Dictionary<string, object> payload = new Dictionary<string, object>
{
{ "amount", order.Amount },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDe

protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
var payload = await GetNoncePayloadAsync();
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
var payload = await GetNoncePayloadAsync();
string orderType = "/exchange/";
if (order.OrderType == OrderType.Market)
{
Expand Down
3 changes: 2 additions & 1 deletion src/ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ protected override async Task<Dictionary<string, decimal>> OnGetAmountsAvailable

protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
var orderType = 0;
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
var orderType = 0;
var side = order.IsBuy? 0: 1;
switch (order.OrderType)
{
Expand Down
5 changes: 3 additions & 2 deletions src/ExchangeSharp/API/Exchanges/OKGroup/OKGroupCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,9 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
{
payload["price"] = outputPrice;
payload["amount"] = outputQuantity;
}
order.ExtraParameters.CopyTo(payload);
if (order.IsPostOnly == true) payload["order_type"] = "1"; // Specify 0: Normal order (Unfilled and 0 imply normal limit order) 1: Post only 2: Fill or Kill 3: Immediate Or Cancel
}
order.ExtraParameters.CopyTo(payload);

JToken obj = await MakeJsonRequestAsync<JToken>("/trade.do", BaseUrl, payload, "POST");
order.Amount = outputQuantity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,8 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
"rate", orderPrice.ToStringInvariant(),
"amount", orderAmount.ToStringInvariant()
};
foreach (KeyValuePair<string, object> kv in order.ExtraParameters)
if (order.IsPostOnly != null) { orderParams.Add("postOnly"); orderParams.Add(order.IsPostOnly.Value ? "1" : "0"); } // (optional) Set to "1" if you want this sell order to only be placed if no portion of it fills immediately.
foreach (KeyValuePair<string, object> kv in order.ExtraParameters)
{
orderParams.Add(kv.Key);
orderParams.Add(kv.Value);
Expand Down
3 changes: 2 additions & 1 deletion src/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDe

protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order)
{
var payload = await GetNoncePayloadAsync();
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
var payload = await GetNoncePayloadAsync();
payload.Add("method", "Trade");
payload.Add("pair", order.MarketSymbol);
payload.Add("type", order.IsBuy ? "buy" : "sell");
Expand Down
1 change: 1 addition & 0 deletions src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ public virtual async Task<Dictionary<string, decimal>> GetMarginAmountsAvailable
public virtual async Task<ExchangeOrderResult> PlaceOrderAsync(ExchangeOrderRequest order)
{
// *NOTE* do not wrap in CacheMethodCall
if (order.IsPostOnly != null) throw new NotImplementedException("Post Only orders are not supported by this exchange or not implemented in ExchangeSharp. Please submit a PR if you are interested in this feature.");
await new SynchronizationContextRemover();
order.MarketSymbol = NormalizeMarketSymbol(order.MarketSymbol);
return await OnPlaceOrderAsync(order);
Expand Down
6 changes: 3 additions & 3 deletions src/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider
Task<ExchangeOrderResult[]> PlaceOrdersAsync(params ExchangeOrderRequest[] orders);

/// <summary>
/// Get details of an order
/// Get details of an order, searching by order id
/// </summary>
/// <param name="orderId">order id</param>
/// <param name="orderId">order id to search for (either server assigned id or client provided id</param>
/// <param name="marketSymbol">Market Symbol</param>
/// <param name="isClientOrderId"></param>
/// <param name="isClientOrderId">Whether the order id parameter is the server assigned id or client provided id</param>
/// <returns>Order details</returns>
Task<ExchangeOrderResult> GetOrderDetailsAsync(string orderId, string? marketSymbol = null, bool isClientOrderId = false);

Expand Down
20 changes: 13 additions & 7 deletions src/ExchangeSharp/Model/ExchangeOrderRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,18 @@ public class ExchangeOrderRequest
/// </summary>
public OrderType OrderType { get; set; } = OrderType.Limit;

/// <summary>
/// Additional order parameters specific to the exchange that don't fit in common order properties. These will be forwarded on to the exchange as key=value pairs.
/// Not all exchanges will use this dictionary.
/// These are added after all other parameters and will replace existing properties, such as order type.
/// </summary>
public Dictionary<string, object> ExtraParameters { get; private set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// a.k.a. MakerOnly or BookOrCancel or PendingOrCancelled. Whether the order is "Post Only" (limit order will be added to the order book and not match with a pre-existing order)
/// To leave unspecified, set to null
/// </summary>
public bool? IsPostOnly { get; set; }

/// <summary>
/// Additional order parameters specific to the exchange that don't fit in common order properties. These will be forwarded on to the exchange as key=value pairs.
/// Not all exchanges will use this dictionary.
/// These are added after all other parameters and will replace existing properties, such as order type.
/// </summary>
public Dictionary<string, object> ExtraParameters { get; private set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Return a rounded amount if needed
Expand Down Expand Up @@ -112,5 +118,5 @@ public enum OrderType
/// A stop order, you will sell if price reaches a low enough level down to a limit
/// </summary>
Stop
}
}
}