future-private: add placeTpSlOrder request

This commit is contained in:
mahdi msr 2025-09-29 00:58:02 +03:30
parent 526ef64ad8
commit bf185918e1
5 changed files with 635 additions and 1 deletions

View File

@ -185,6 +185,38 @@ if ($response->getStatusCode() === 200) {
} }
``` ```
### Place TP/SL Order
```php
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
$api = app(PlaceTpSlOrderRequestContract::class);
// Place TP/SL order with both take profit and stop loss
$response = $api->placeTpSlOrder(
'BTCUSDT', // symbol
'111', // positionId
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE', // slStopType
'LIMIT', // tpOrderType
'50000.1', // tpOrderPrice
'LIMIT', // slOrderType
'45000.1', // slOrderPrice
'1', // tpQty
'1' // slQty
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "TP/SL order placed successfully!";
echo "Order ID: " . $data['data']['orderId'];
}
}
```
### Get Future Kline Data ### Get Future Kline Data
```php ```php
@ -210,6 +242,7 @@ if ($response->getStatusCode() === 200) {
### Trading ### Trading
- `placeOrder(...)` - Place a new order with full support for all order types, take profit, stop loss, and position management - `placeOrder(...)` - Place a new order with full support for all order types, take profit, stop loss, and position management
- `placeTpSlOrder(...)` - Place TP/SL order for existing positions
- `flashClosePosition(string $positionId)` - Flash close position by position ID - `flashClosePosition(string $positionId)` - Flash close position by position ID
### Position Management ### Position Management
@ -235,6 +268,7 @@ if ($response->getStatusCode() === 200) {
- **Change Margin Mode**: 10 req/sec/uid - **Change Margin Mode**: 10 req/sec/uid
- **Get Single Account**: 10 req/sec/uid - **Get Single Account**: 10 req/sec/uid
- **Place Order**: 10 req/sec/uid - **Place Order**: 10 req/sec/uid
- **Place TP/SL Order**: 10 req/sec/uid
- **Flash Close Position**: 5 req/sec/uid - **Flash Close Position**: 5 req/sec/uid
- **Get Pending Positions**: 10 req/sec/uid - **Get Pending Positions**: 10 req/sec/uid
@ -276,6 +310,7 @@ vendor/bin/pest tests/ChangeLeverageTest.php
vendor/bin/pest tests/ChangeMarginModeTest.php vendor/bin/pest tests/ChangeMarginModeTest.php
vendor/bin/pest tests/GetSingleAccountTest.php vendor/bin/pest tests/GetSingleAccountTest.php
vendor/bin/pest tests/PlaceOrderTest.php vendor/bin/pest tests/PlaceOrderTest.php
vendor/bin/pest tests/PlaceTpSlOrderTest.php
vendor/bin/pest tests/FlashClosePositionTest.php vendor/bin/pest tests/FlashClosePositionTest.php
vendor/bin/pest tests/GetPendingPositionsTest.php vendor/bin/pest tests/GetPendingPositionsTest.php
vendor/bin/pest tests/HeaderTest.php vendor/bin/pest tests/HeaderTest.php
@ -289,6 +324,7 @@ See the `examples/` directory for complete usage examples:
- `ChangeMarginModeExample.php` - `ChangeMarginModeExample.php`
- `GetSingleAccountExample.php` - `GetSingleAccountExample.php`
- `PlaceOrderExample.php` - `PlaceOrderExample.php`
- `PlaceTpSlOrderExample.php`
- `FlashClosePositionExample.php` - `FlashClosePositionExample.php`
- `GetPendingPositionsExample.php` - `GetPendingPositionsExample.php`

View File

@ -0,0 +1,247 @@
<?php
/**
* Example usage of Place TP/SL Order functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to place TP/SL orders on Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
// Example configuration (in real usage, these would be in your .env file)
config([
'bitunix-api.future_base_uri' => 'https://fapi.bitunix.com/',
'bitunix-api.api_key' => 'your-api-key-here',
'bitunix-api.api_secret' => 'your-api-secret-here',
'bitunix-api.language' => 'en-US',
]);
try {
// Get the API instance
$api = app(PlaceTpSlOrderRequestContract::class);
echo "🎯 Place TP/SL Order Examples\n\n";
// Example 1: Place TP/SL order with both take profit and stop loss
echo "1. Placing TP/SL order with both TP and SL...\n";
$response = $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE', // slStopType
'LIMIT', // tpOrderType
'50000.1', // tpOrderPrice
'LIMIT', // slOrderType
'45000.1', // slOrderPrice
'1', // tpQty
'1' // slQty
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ TP/SL order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 2: Place TP/SL order with take profit only
echo "2. Placing TP/SL order with take profit only...\n";
$response = $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'MARK_PRICE', // tpStopType
null, // slPrice
null, // slStopType
'MARKET', // tpOrderType
null, // tpOrderPrice
null, // slOrderType
null, // slOrderPrice
'1', // tpQty
null // slQty
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ TP order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 3: Place TP/SL order with stop loss only
echo "3. Placing TP/SL order with stop loss only...\n";
$response = $api->placeTpSlOrder(
'BTCUSDT',
'111',
null, // tpPrice
null, // tpStopType
'45000', // slPrice
'MARK_PRICE', // slStopType
null, // tpOrderType
null, // tpOrderPrice
'MARKET', // slOrderType
null, // slOrderPrice
null, // tpQty
'1' // slQty
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ SL order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 4: Place TP/SL order with different symbols
echo "4. Placing TP/SL orders for different symbols...\n";
$symbols = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($symbols as $symbol) {
echo "Placing TP/SL order for {$symbol}...\n";
$response = $api->placeTpSlOrder(
$symbol,
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE',
'LIMIT',
'50000.1',
'LIMIT',
'45000.1',
'1',
'1'
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "{$symbol} TP/SL order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo "❌ Failed to place {$symbol} TP/SL order: ".$data['msg']."\n";
}
} else {
echo "❌ HTTP Error for {$symbol}: ".$response->getStatusCode()."\n";
}
}
echo "\n";
// Example 5: Place TP/SL order with different position IDs
echo "5. Placing TP/SL orders for different position IDs...\n";
$positionIds = ['111', '222', '333'];
foreach ($positionIds as $positionId) {
echo "Placing TP/SL order for position ID {$positionId}...\n";
$response = $api->placeTpSlOrder(
'BTCUSDT',
$positionId,
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE',
'LIMIT',
'50000.1',
'LIMIT',
'45000.1',
'1',
'1'
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position {$positionId} TP/SL order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo "❌ Failed to place position {$positionId} TP/SL order: ".$data['msg']."\n";
}
} else {
echo "❌ HTTP Error for position {$positionId}: ".$response->getStatusCode()."\n";
}
}
echo "\n";
// Example 6: Error handling
echo "6. Error handling example...\n";
$response = $api->placeTpSlOrder('INVALID', '111');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ TP/SL order placed successfully!\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
echo "This is expected for invalid symbol\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
} catch (Exception $e) {
echo '❌ Exception: '.$e->getMessage()."\n";
}
/**
* Place TP/SL Order Features:
*
* - Place TP/SL orders for existing positions
* - Rate limit: 10 req/sec/UID
* - Supports both take profit and stop loss
* - Flexible parameter configuration
*
* Required Parameters:
* - symbol: Trading pair
* - positionId: Position ID associated with TP/SL
*
* Optional Parameters:
* - tpPrice: Take-profit trigger price
* - tpStopType: Take-profit trigger type (LAST_PRICE/MARK_PRICE)
* - slPrice: Stop-loss trigger price
* - slStopType: Stop-loss trigger type (LAST_PRICE/MARK_PRICE)
* - tpOrderType: Take-profit order type (LIMIT/MARKET)
* - tpOrderPrice: Take-profit order price
* - slOrderType: Stop-loss order type (LIMIT/MARKET)
* - slOrderPrice: Stop-loss order price
* - tpQty: Take-profit order quantity (base coin)
* - slQty: Stop-loss order quantity (base coin)
*
* Note: At least one of tpPrice or slPrice is required.
* At least one of tpQty or slQty is required.
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

View File

@ -11,9 +11,10 @@ use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract;
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract; use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
use Msr\LaravelBitunixApi\Requests\Header; use Msr\LaravelBitunixApi\Requests\Header;
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract; use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, PlaceOrderRequestContract class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, PlaceOrderRequestContract, PlaceTpSlOrderRequestContract
{ {
private Client $publicFutureClient; private Client $publicFutureClient;
@ -201,4 +202,62 @@ class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginMo
return $response; return $response;
} }
public function placeTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slOrderType = null,
?string $slOrderPrice = null,
?string $tpQty = null,
?string $slQty = null
): ResponseInterface {
$body = [
'symbol' => $symbol,
'positionId' => $positionId,
];
// Add optional parameters if provided
if ($tpPrice !== null) {
$body['tpPrice'] = $tpPrice;
}
if ($tpStopType !== null) {
$body['tpStopType'] = $tpStopType;
}
if ($slPrice !== null) {
$body['slPrice'] = $slPrice;
}
if ($slStopType !== null) {
$body['slStopType'] = $slStopType;
}
if ($tpOrderType !== null) {
$body['tpOrderType'] = $tpOrderType;
}
if ($tpOrderPrice !== null) {
$body['tpOrderPrice'] = $tpOrderPrice;
}
if ($slOrderType !== null) {
$body['slOrderType'] = $slOrderType;
}
if ($slOrderPrice !== null) {
$body['slOrderPrice'] = $slOrderPrice;
}
if ($tpQty !== null) {
$body['tpQty'] = $tpQty;
}
if ($slQty !== null) {
$body['slQty'] = $slQty;
}
$response = $this->getPrivateFutureClient([], $body)->post('tpsl/place_order', [
'json' => $body,
]);
return $response;
}
} }

View File

@ -0,0 +1,39 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface PlaceTpSlOrderRequestContract
{
/**
* Place TP/SL Order
*
* @param string $symbol Trading pair
* @param string $positionId Position ID associated with take-profit and stop-loss
* @param string|null $tpPrice Take-profit trigger price
* @param string|null $tpStopType Take-profit trigger type (LAST_PRICE/MARK_PRICE)
* @param string|null $slPrice Stop-loss trigger price
* @param string|null $slStopType Stop-loss trigger type (LAST_PRICE/MARK_PRICE)
* @param string|null $tpOrderType Take-profit order type (LIMIT/MARKET)
* @param string|null $tpOrderPrice Take-profit order price
* @param string|null $slOrderType Stop-loss order type (LIMIT/MARKET)
* @param string|null $slOrderPrice Stop-loss order price
* @param string|null $tpQty Take-profit order quantity (base coin)
* @param string|null $slQty Stop-loss order quantity (base coin)
*/
public function placeTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slOrderType = null,
?string $slOrderPrice = null,
?string $tpQty = null,
?string $slQty = null
): ResponseInterface;
}

View File

@ -0,0 +1,253 @@
<?php
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
beforeEach(function () {
config([
'bitunix-api.future_base_uri' => 'https://fapi.bitunix.com/',
'bitunix-api.api_key' => 'test-api-key',
'bitunix-api.api_secret' => 'test-secret-key',
'bitunix-api.language' => 'en-US',
]);
});
it('can place TP/SL order with required parameters only', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
expect(fn() => $api->placeTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
});
it('can place TP/SL order with take profit only', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE', // tpStopType
null, // slPrice
null, // slStopType
'LIMIT', // tpOrderType
'50000.1', // tpOrderPrice
null, // slOrderType
null, // slOrderPrice
'1', // tpQty
null // slQty
))->not->toThrow(Exception::class);
});
it('can place TP/SL order with stop loss only', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
null, // tpPrice
null, // tpStopType
'45000', // slPrice
'LAST_PRICE', // slStopType
null, // tpOrderType
null, // tpOrderPrice
'LIMIT', // slOrderType
'45000.1', // slOrderPrice
null, // tpQty
'1' // slQty
))->not->toThrow(Exception::class);
});
it('can place TP/SL order with both take profit and stop loss', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE', // slStopType
'LIMIT', // tpOrderType
'50000.1', // tpOrderPrice
'LIMIT', // slOrderType
'45000.1', // slOrderPrice
'1', // tpQty
'1' // slQty
))->not->toThrow(Exception::class);
});
it('validates place TP/SL order method exists', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
expect(method_exists($api, 'placeTpSlOrder'))->toBeTrue();
});
it('can handle different symbol formats', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$symbols = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($symbols as $symbol) {
expect(fn() => $api->placeTpSlOrder($symbol, '111'))
->not->toThrow(Exception::class);
}
});
it('can handle different position ID formats', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$positionIds = ['111', 'position-123', '19848247723672'];
foreach ($positionIds as $positionId) {
expect(fn() => $api->placeTpSlOrder('BTCUSDT', $positionId))
->not->toThrow(Exception::class);
}
});
it('can handle different stop types', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$stopTypes = ['LAST_PRICE', 'MARK_PRICE'];
foreach ($stopTypes as $stopType) {
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000',
$stopType,
'45000',
$stopType
))->not->toThrow(Exception::class);
}
});
it('can handle different order types', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$orderTypes = ['LIMIT', 'MARKET'];
foreach ($orderTypes as $orderType) {
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE',
$orderType,
'50000.1',
$orderType,
'45000.1'
))->not->toThrow(Exception::class);
}
});
it('can handle different quantity formats', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$quantities = ['1', '0.1', '10.5', '100'];
foreach ($quantities as $qty) {
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE',
'LIMIT',
'50000.1',
'LIMIT',
'45000.1',
$qty,
$qty
))->not->toThrow(Exception::class);
}
});
it('can handle different price formats', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$prices = ['50000', '50000.1', '50000.01', '50000.001'];
foreach ($prices as $price) {
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
$price,
'LAST_PRICE',
'45000',
'LAST_PRICE',
'LIMIT',
$price,
'LIMIT',
'45000.1'
))->not->toThrow(Exception::class);
}
});
it('can handle multiple place TP/SL order calls', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
$calls = [
['BTCUSDT', '111'],
['ETHUSDT', '222', '3000', 'LAST_PRICE'],
['ADAUSDT', '333', null, null, '0.5', 'LAST_PRICE'],
['BTCUSDT', '444', '50000', 'LAST_PRICE', '45000', 'LAST_PRICE', 'LIMIT', '50000.1', 'LIMIT', '45000.1', '1', '1'],
];
foreach ($calls as $params) {
expect(fn() => $api->placeTpSlOrder(...$params))
->not->toThrow(Exception::class);
}
});
it('validates place TP/SL order response structure', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
// This test verifies the method can be called without throwing exceptions
// The actual response structure will be validated by the API
expect(fn() => $api->placeTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
});
it('can handle edge cases for TP/SL order', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
// Test with minimum required parameters
expect(fn() => $api->placeTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
// Test with all parameters
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE',
'LIMIT',
'50000.1',
'LIMIT',
'45000.1',
'1',
'1'
))->not->toThrow(Exception::class);
});
it('can handle special characters in parameters', function () {
$api = app(PlaceTpSlOrderRequestContract::class);
// Test with special characters in position ID
expect(fn() => $api->placeTpSlOrder('BTCUSDT', 'pos-123-abc'))
->not->toThrow(Exception::class);
// Test with decimal prices
expect(fn() => $api->placeTpSlOrder(
'BTCUSDT',
'111',
'50000.123',
'LAST_PRICE',
'45000.456',
'LAST_PRICE'
))->not->toThrow(Exception::class);
});