Skip to content

Commit 9e7029e

Browse files
committed
replaceOrder added
1 parent f7b8fbb commit 9e7029e

2 files changed

Lines changed: 97 additions & 26 deletions

File tree

php-binance-api.php

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,7 +1324,7 @@ public function rollingWindowPriceChange(?string $symbol = null, array $symbols
13241324
} elseif (is_array($symbols) && !empty($symbols)) {
13251325
$request['symbols'] = json_encode($symbols);
13261326
} else {
1327-
throw new \Exception("tradingDay(): Either symbol or symbols must be set");
1327+
throw new \Exception("rollingWindowPriceChange(): Either symbol or symbols must be set");
13281328
}
13291329
if (!is_null($windowSize)) {
13301330
$request['windowSize'] = $windowSize;
@@ -1453,7 +1453,7 @@ public function balances(string $market_type = 'spot', array $params = [], strin
14531453
} else if ($api_version === 'v3') {
14541454
$url = "v3/balance";
14551455
} else {
1456-
throw new \Exception("Invalid API version specified. Use 'v2' or 'v3'.");
1456+
throw new \Exception("balances(): Invalid API version specified. Use 'v2' or 'v3'.");
14571457
}
14581458
}
14591459
$response = $this->httpRequest($url, "GET", array_merge($request, $params), true);
@@ -1892,6 +1892,25 @@ public function get_headers_from_curl_response(string $header)
18921892
* @throws \Exception
18931893
*/
18941894
public function order(string $side, string $symbol, $quantity, $price, string $type = "LIMIT", array $params = [], bool $test = false)
1895+
{
1896+
$request = $this->createSpotOrderRequest($side, $symbol, $quantity, $price, $type, $params);
1897+
$sor = false;
1898+
if (isset($request['sor'])) {
1899+
$sor = $request['sor'];
1900+
unset($request['sor']);
1901+
}
1902+
$url = $sor ? "v3/sor/order" : "v3/order";
1903+
if ($test) {
1904+
$url = $url . "/test";
1905+
}
1906+
return $this->apiRequest($url, "POST", $request, true); // sending only $request cuz $params are already added to $request inside createSpotOrderRequest
1907+
}
1908+
1909+
/**
1910+
* createSpotOrderRequest
1911+
* helper function to create a request for the spot order
1912+
*/
1913+
protected function createSpotOrderRequest(string $side, string $symbol, $quantity, $price, string $type = "LIMIT", array $params = [])
18951914
{
18961915
$request = [
18971916
"symbol" => $symbol,
@@ -1946,16 +1965,7 @@ public function order(string $side, string $symbol, $quantity, $price, string $t
19461965
} else {
19471966
$request['newClientOrderId'] = $this->generateSpotClientOrderId();
19481967
}
1949-
$sor = false;
1950-
if (isset($params['sor'])) {
1951-
$sor = $params['sor'];
1952-
unset($params['sor']);
1953-
}
1954-
$url = $sor ? "v3/sor/order" : "v3/order";
1955-
if ($test) {
1956-
$url = $url . "/test";
1957-
}
1958-
return $this->apiRequest($url, "POST", array_merge($request, $params), true);
1968+
return array_merge($request, $params);
19591969
}
19601970

19611971
/**
@@ -1990,6 +2000,46 @@ public function sorOrder(string $side, string $symbol, $quantity, $price, string
19902000
return $this->order($side, $symbol, $quantity, $price, $type, $params, $test);
19912001
}
19922002

2003+
/**
2004+
* replaceOrder - cancels an existing order and places a new order on the same symbol
2005+
*
2006+
* @link https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
2007+
*
2008+
* @param string $side (mandatory) typically "BUY" or "SELL"
2009+
* @param string $symbol (mandatory) to buy or sell
2010+
* @param string $quantity (mandatory) in the order
2011+
* @param string $price (optional) for the order
2012+
* @param string $type (optional) is determined by the symbol bu typicall LIMIT, STOP_LOSS_LIMIT etc. (default is LIMIT)
2013+
* @param string $cancelOrderId (optional) the orderId of the order to be replaced (mandatory if cancelOrigClientOrderId is not set)
2014+
* @param string $cancelOrigClientOrderId (optional) the origClientOrderId of the order to be replaced (mandatory if cancelOrderId is not set)
2015+
* @param bool $allowFailure (optional) if true new order placement will be attempted even if cancel request fails (default is false)
2016+
* @param string $cancelRestrictions (optional) ONLY_NEW (cancel will succeed if the order status is NEW) or ONLY_PARTIALLY_FILLED (cancel will succeed if order status is PARTIALLY_FILLED)
2017+
* @param string $orderRateLimitExceededMode (optional) DO_NOTHING (default - will only attempt to cancel the order if account has not exceeded the unfilled order rate limit) or CANCEL_ONLY (will always cancel the order)
2018+
* @param array $params (optional) additional transaction options (same as for order())
2019+
*
2020+
* returns array containing the response
2021+
* @throws \Exception
2022+
*/
2023+
public function replaceOrder(string $side, string $symbol, $quantity, $price, string $type = "LIMIT", ?string $cancelOrderId = null, string $cancelOrigClientOrderId = null, bool $allowFailure = null, string $cancelRestrictions = null, string $orderRateLimitExceededMode = null, array $params = [])
2024+
{
2025+
$request = $this->createSpotOrderRequest($side, $symbol, $quantity, $price, $type, $params);
2026+
if (!is_null($cancelOrderId)) {
2027+
$request['cancelOrderId'] = $cancelOrderId;
2028+
} else if (is_null($cancelOrigClientOrderId)) {
2029+
throw new \Exception("replaceOrder(): Either cancelOrderId or cancelOrigClientOrderId must be set");
2030+
}
2031+
if (!is_null($cancelOrigClientOrderId)) {
2032+
$request['cancelOrigClientOrderId'] = $cancelOrigClientOrderId;
2033+
}
2034+
$request['cancelReplaceMode'] = $allowFailure ? 'ALLOW_FAILURE' : 'STOP_ON_FAILURE';
2035+
if (!is_null($cancelRestrictions)) {
2036+
$request['cancelRestrictions'] = $cancelRestrictions;
2037+
}
2038+
if (!is_null($orderRateLimitExceededMode)) {
2039+
$request['orderRateLimitExceededMode'] = $orderRateLimitExceededMode;
2040+
}
2041+
return $this->apiRequest("v3/order/cancelReplace", "POST", $request, true); // only request is needed, params are already added to request inside createSpotOrderRequest
2042+
}
19932043
/**
19942044
* candlesticks get the candles for the given intervals
19952045
* 1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1M
@@ -4750,10 +4800,10 @@ public function futuresBatchOrders(array $orders, array $params = [])
47504800
{
47514801
$formatedOrders = $this->createBatchOrdersRequest($orders);
47524802
if (count($formatedOrders) > 5) {
4753-
throw new \Exception('futuresBatchOrders: max 5 orders allowed');
4803+
throw new \Exception('futuresBatchOrders(): max 5 orders allowed');
47544804
}
47554805
if (count($formatedOrders) < 1) {
4756-
throw new \Exception('futuresBatchOrders: at least 1 order required');
4806+
throw new \Exception('futuresBatchOrders(): at least 1 order required');
47574807
}
47584808
$request = [];
47594809

@@ -4789,7 +4839,7 @@ public function futuresEditOrder(string $symbol, string $side, string $quantity,
47894839
$request['origClientOrderId'] = $origClientOrderId;
47904840
}
47914841
if (!$origClientOrderId && !$orderId) {
4792-
throw new \Exception('futuresEditOrder: either orderId or origClientOrderId must be set');
4842+
throw new \Exception('futuresEditOrder(): either orderId or origClientOrderId must be set');
47934843
}
47944844
if ($orderId) {
47954845
$request['orderId'] = $orderId;
@@ -4888,7 +4938,7 @@ public function futuresCancel(string $symbol, $orderid, $params = [])
48884938
if ($orderid) {
48894939
$request['orderId'] = $orderid;
48904940
} else if (!isset($params['origClientOrderId'])) {
4891-
throw new \Exception('futuresCancel: either orderId or origClientOrderId must be set');
4941+
throw new \Exception('futuresCancel(): either orderId or origClientOrderId must be set');
48924942
}
48934943
return $this->fapiRequest("v1/order", 'DELETE', array_merge($request, $params), true);
48944944
}
@@ -4921,7 +4971,7 @@ public function futuresCancelBatchOrders(string $symbol, $orderIdList = null, $o
49214971
// remove spaces between the ids
49224972
$request['origClientOrderIdList'] = str_replace(', ', ',', json_encode($origClientOrderIdList));
49234973
} else {
4924-
throw new \Exception('futuresCancelBatchOrders: either orderIdList or origClientOrderIdList must be set');
4974+
throw new \Exception('futuresCancelBatchOrders(): either orderIdList or origClientOrderIdList must be set');
49254975
}
49264976

49274977
return $this->fapiRequest("v1/batchOrders", 'DELETE', array_merge($request, $params), true);
@@ -5000,7 +5050,7 @@ public function futuresOrderStatus(string $symbol, $orderId = null, $origClientO
50005050
} else if ($origClientOrderId) {
50015051
$request['origClientOrderId'] = $origClientOrderId;
50025052
} else {
5003-
throw new \Exception('futuresOrderStatus: either orderId or origClientOrderId must be set');
5053+
throw new \Exception('futuresOrderStatus(): either orderId or origClientOrderId must be set');
50045054
}
50055055

50065056
return $this->fapiRequest("v1/order", 'GET', array_merge($request, $params), true);
@@ -5097,7 +5147,7 @@ public function futuresOpenOrder(string $symbol, $orderId = null, $origClientOrd
50975147
} else if ($origClientOrderId) {
50985148
$request['origClientOrderId'] = $origClientOrderId;
50995149
} else {
5100-
throw new \Exception('futuresOpenOrder: either orderId or origClientOrderId must be set');
5150+
throw new \Exception('futuresOpenOrder(): either orderId or origClientOrderId must be set');
51015151
}
51025152

51035153
return $this->fapiRequest("v1/openOrder", 'GET', array_merge($request, $params), true);
@@ -5439,7 +5489,7 @@ public function futuresPositions($symbol = null, array $params = [], string $api
54395489
}
54405490

54415491
if ($api_version !== 'v2' && $api_version !== 'v3') {
5442-
throw new \Exception('futuresPositions: api_version must be either v2 or v3');
5492+
throw new \Exception('futuresPositions(): api_version must be either v2 or v3');
54435493
}
54445494
return $this->fapiRequest($api_version . "/positionRisk", 'GET', array_merge($request, $params), true);
54455495
}
@@ -5568,10 +5618,10 @@ public function futuresPositionMarginChangeHistory(string $symbol, $startTime =
55685618
} else if ($addOrReduce === 'REDUCE' || $addOrReduce === '2') {
55695619
$request['addOrReduce'] = 2;
55705620
} else {
5571-
throw new \Exception('futuresPositionMarginChangeHistory: addOrReduce must be "ADD" or "REDUCE" or 1 or 2');
5621+
throw new \Exception('futuresPositionMarginChangeHistory(): addOrReduce must be "ADD" or "REDUCE" or 1 or 2');
55725622
}
55735623
} else {
5574-
throw new \Exception('futuresPositionMarginChangeHistory: addOrReduce must be "ADD" or "REDUCE" or 1 or 2');
5624+
throw new \Exception('futuresPositionMarginChangeHistory(): addOrReduce must be "ADD" or "REDUCE" or 1 or 2');
55755625
}
55765626
}
55775627

@@ -5597,7 +5647,7 @@ public function futuresPositionMarginChangeHistory(string $symbol, $startTime =
55975647
public function futuresBalances(array $params = [], string $api_version = 'v3')
55985648
{
55995649
if ($api_version !== 'v2' && $api_version !== 'v3') {
5600-
throw new \Exception('futuresBalances: api_version must be either v2 or v3');
5650+
throw new \Exception('futuresBalances(): api_version must be either v2 or v3');
56015651
}
56025652
return $this->balances('futures', $params, 'v3');
56035653
}
@@ -5640,7 +5690,7 @@ public function futuresBalancesV3(array $params = [])
56405690
public function futuresAccount(array $params = [], string $api_version = 'v3')
56415691
{
56425692
if ($api_version !== 'v2' && $api_version !== 'v3') {
5643-
throw new \Exception('futuresAccount: api_version must be either v2 or v3');
5693+
throw new \Exception('futuresAccount(): api_version must be either v2 or v3');
56445694
}
56455695

56465696
return $this->fapiRequest($api_version . "/account", "GET", $params, true);
@@ -6150,7 +6200,7 @@ public function convertSend(string $fromAsset, string $toAsset, $fromAmount = nu
61506200
} else if ($toAmount) {
61516201
$request['toAmount'] = $toAmount;
61526202
} else {
6153-
throw new \Exception('convertSendRequest: fromAmount or toAmount must be set');
6203+
throw new \Exception('convertSendRequest(): fromAmount or toAmount must be set');
61546204
}
61556205
if ($validTime) {
61566206
$request['validTime'] = $validTime;
@@ -6206,7 +6256,7 @@ public function convertStatus($orderId = null, $quoteId = null, array $params =
62066256
} else if ($quoteId) {
62076257
$request['quoteId'] = $quoteId;
62086258
} else {
6209-
throw new \Exception('convertStatus: orderId or quoteId must be set');
6259+
throw new \Exception('convertStatus(): orderId or quoteId must be set');
62106260
}
62116261
return $this->fapiRequest("v1/convert/orderStatus", 'GET', array_merge($request, $params), true);
62126262
}

tests/BinanceStaticTests.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,27 @@ public function testSpotSorOrder()
143143
$this->assertTrue(str_starts_with($params['newClientOrderId'], $this->SPOT_ORDER_PREFIX));
144144
}
145145

146+
public function testSpotReplaceOrder()
147+
{
148+
try {
149+
$this->binance->replaceOrder('BUY', 'BTCUSDT', 1, 1000, 'LIMIT', '123456789');
150+
} catch(\Throwable $e) {
151+
152+
}
153+
$this->assertEquals("https://api.binance.com/api/v3/order/cancelReplace", self::$capturedUrl);
154+
155+
parse_str(self::$capturedBody, $params);
156+
157+
$this->assertEquals("BTCUSDT", $params['symbol']);
158+
$this->assertEquals("BUY", $params['side']);
159+
$this->assertEquals("LIMIT", $params['type']);
160+
$this->assertEquals(1, $params['quantity']);
161+
$this->assertEquals(1000, $params['price']);
162+
$this->assertEquals("GTC", $params['timeInForce']);
163+
$this->assertEquals('123456789', $params['cancelOrderId']);
164+
$this->assertTrue(str_starts_with($params['newClientOrderId'], $this->SPOT_ORDER_PREFIX));
165+
}
166+
146167
public function testSpotBuy()
147168
{
148169
try {

0 commit comments

Comments
 (0)