From e86854ca54c6266d5707fc5e8348437d34871356 Mon Sep 17 00:00:00 2001 From: mahdi msr Date: Sun, 28 Sep 2025 23:58:03 +0330 Subject: [PATCH] future-private: add flash close position request --- README.md | 21 ++++ examples/FlashClosePositionExample.php | 117 ++++++++++++++++++ src/LaravelBitunixApi.php | 16 ++- src/LaravelBitunixApiServiceProvider.php | 2 + .../FlashClosePositionRequestContract.php | 16 +++ tests/FlashClosePositionTest.php | 111 +++++++++++++++++ 6 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 examples/FlashClosePositionExample.php create mode 100644 src/Requests/FlashClosePositionRequestContract.php create mode 100644 tests/FlashClosePositionTest.php diff --git a/README.md b/README.md index 6dde83b..9707825 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,23 @@ if ($response->getStatusCode() === 200) { } ``` +### Flash Close Position + +```php +use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract; + +$api = app(FlashClosePositionRequestContract::class); +$response = $api->flashClosePosition('19848247723672'); + +if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "Position flash closed successfully!"; + echo "Position ID: " . $data['data']['positionId']; + } +} +``` + ### Get Future Kline Data ```php @@ -133,6 +150,7 @@ if ($response->getStatusCode() === 200) { ### Trading - `placeOrder(...)` - Place a new order with full support for all order types, take profit, stop loss, and position management +- `flashClosePosition(string $positionId)` - Flash close position by position ID ### Market Data @@ -152,6 +170,7 @@ if ($response->getStatusCode() === 200) { - **Change Leverage**: 10 req/sec/uid - **Change Margin Mode**: 10 req/sec/uid - **Place Order**: 10 req/sec/uid +- **Flash Close Position**: 5 req/sec/uid ## Error Handling @@ -190,6 +209,7 @@ Run specific tests: vendor/bin/pest tests/ChangeLeverageTest.php vendor/bin/pest tests/ChangeMarginModeTest.php vendor/bin/pest tests/PlaceOrderTest.php +vendor/bin/pest tests/FlashClosePositionTest.php vendor/bin/pest tests/HeaderTest.php ``` @@ -200,6 +220,7 @@ See the `examples/` directory for complete usage examples: - `ChangeLeverageExample.php` - `ChangeMarginModeExample.php` - `PlaceOrderExample.php` +- `FlashClosePositionExample.php` ## Troubleshooting diff --git a/examples/FlashClosePositionExample.php b/examples/FlashClosePositionExample.php new file mode 100644 index 0000000..aa76ef1 --- /dev/null +++ b/examples/FlashClosePositionExample.php @@ -0,0 +1,117 @@ + '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(FlashClosePositionRequestContract::class); + + echo "⚡ Flash Close Position Examples\n\n"; + + // Example 1: Flash close a single position + echo "1. Flash closing position...\n"; + $positionId = '19848247723672'; + + $response = $api->flashClosePosition($positionId); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ Position flash closed successfully!\n"; + echo "Position ID: " . $data['data']['positionId'] . "\n"; + } else { + echo "❌ API Error: " . $data['msg'] . "\n"; + } + } else { + echo "❌ HTTP Error: " . $response->getStatusCode() . "\n"; + } + + echo "\n"; + + // Example 2: Flash close multiple positions + echo "2. Flash closing multiple positions...\n"; + $positionIds = [ + '19848247723672', + '19848247723673', + '19848247723674' + ]; + + foreach ($positionIds as $positionId) { + echo "Closing position: {$positionId}...\n"; + + $response = $api->flashClosePosition($positionId); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ Position {$positionId} closed successfully!\n"; + } else { + echo "❌ Failed to close position {$positionId}: " . $data['msg'] . "\n"; + } + } else { + echo "❌ HTTP Error for position {$positionId}: " . $response->getStatusCode() . "\n"; + } + } + + echo "\n"; + + // Example 3: Error handling + echo "3. Error handling example...\n"; + $invalidPositionId = 'invalid-position-id'; + + $response = $api->flashClosePosition($invalidPositionId); + + if ($response->getStatusCode() === 200) { + $data = json_decode($response->getBody()->getContents(), true); + if ($data['code'] === 0) { + echo "✅ Position closed successfully!\n"; + } else { + echo "❌ API Error: " . $data['msg'] . "\n"; + echo "This is expected for invalid position ID\n"; + } + } else { + echo "❌ HTTP Error: " . $response->getStatusCode() . "\n"; + } + +} catch (Exception $e) { + echo '❌ Exception: '.$e->getMessage()."\n"; +} + +/** + * Flash Close Position Features: + * + * - Closes position by position ID + * - Rate limit: 5 req/sec/uid + * - Immediate position closure + * - No additional parameters required + * + * Important Notes: + * + * - Position ID must be valid and exist + * - Position must be open to be closed + * - This is an immediate action (flash close) + * - Use with caution as it closes positions immediately + * + * Environment Variables Required: + * + * BITUNIX_API_KEY=your-api-key + * BITUNIX_API_SECRET=your-api-secret + * BITUNIX_LANGUAGE=en-US + */ diff --git a/src/LaravelBitunixApi.php b/src/LaravelBitunixApi.php index 33d976e..9f3a48c 100644 --- a/src/LaravelBitunixApi.php +++ b/src/LaravelBitunixApi.php @@ -5,12 +5,13 @@ namespace Msr\LaravelBitunixApi; use GuzzleHttp\Client; use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract; use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract; +use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract; use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract; use Msr\LaravelBitunixApi\Requests\Header; use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract; use Psr\Http\Message\ResponseInterface; -class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FutureKLineRequestContract, PlaceOrderRequestContract +class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, PlaceOrderRequestContract { private Client $publicFutureClient; @@ -153,4 +154,17 @@ class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginMo return $response; } + + public function flashClosePosition(string $positionId): ResponseInterface + { + $body = [ + 'positionId' => $positionId, + ]; + + $response = $this->getPrivateFutureClient([], $body)->post('trade/flash_close_position', [ + 'json' => $body, + ]); + + return $response; + } } diff --git a/src/LaravelBitunixApiServiceProvider.php b/src/LaravelBitunixApiServiceProvider.php index 8360620..1b69606 100644 --- a/src/LaravelBitunixApiServiceProvider.php +++ b/src/LaravelBitunixApiServiceProvider.php @@ -5,6 +5,7 @@ namespace Msr\LaravelBitunixApi; use Msr\LaravelBitunixApi\Commands\LaravelBitunixApiCommand; use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract; use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract; +use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract; use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract; use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract; use Spatie\LaravelPackageTools\Package; @@ -35,5 +36,6 @@ class LaravelBitunixApiServiceProvider extends PackageServiceProvider $this->app->bind(ChangeLeverageRequestContract::class, LaravelBitunixApi::class); $this->app->bind(ChangeMarginModeRequestContract::class, LaravelBitunixApi::class); $this->app->bind(PlaceOrderRequestContract::class, LaravelBitunixApi::class); + $this->app->bind(FlashClosePositionRequestContract::class, LaravelBitunixApi::class); } } diff --git a/src/Requests/FlashClosePositionRequestContract.php b/src/Requests/FlashClosePositionRequestContract.php new file mode 100644 index 0000000..ad0117a --- /dev/null +++ b/src/Requests/FlashClosePositionRequestContract.php @@ -0,0 +1,16 @@ + '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 flash close position successfully', function () { + $api = app(FlashClosePositionRequestContract::class); + + expect(fn() => $api->flashClosePosition('19848247723672')) + ->not->toThrow(Exception::class); +}); + +it('validates required position ID parameter', function () { + $api = app(FlashClosePositionRequestContract::class); + + // Test with valid position ID + expect(fn() => $api->flashClosePosition('19848247723672')) + ->not->toThrow(Exception::class) + ->and(fn() => $api->flashClosePosition('123456789')) + ->not->toThrow(Exception::class); +}); + +it('can handle different position ID formats', function () { + $api = app(FlashClosePositionRequestContract::class); + + $positionIds = [ + '19848247723672', + '123456789', + '987654321', + 'position-123', + 'pos_456' + ]; + + foreach ($positionIds as $positionId) { + expect(fn() => $api->flashClosePosition($positionId)) + ->not->toThrow(Exception::class); + } +}); + +it('validates position ID parameter type', function () { + $api = app(FlashClosePositionRequestContract::class); + + // Test with string position ID + expect(fn() => $api->flashClosePosition('19848247723672')) + ->not->toThrow(Exception::class) + ->and(fn() => $api->flashClosePosition('123456789')) + ->not->toThrow(Exception::class); +}); + +it('can handle edge cases for position ID', function () { + $api = app(FlashClosePositionRequestContract::class); + + // Test with long position ID + expect(fn() => $api->flashClosePosition('198482477236721234567890')) + ->not->toThrow(Exception::class) + ->and(fn() => $api->flashClosePosition('123')) + ->not->toThrow(Exception::class); +}); + +it('validates flash close position method exists', function () { + $api = app(FlashClosePositionRequestContract::class); + + expect(method_exists($api, 'flashClosePosition'))->toBeTrue(); +}); + +it('can handle multiple flash close position calls', function () { + $api = app(FlashClosePositionRequestContract::class); + + $positionIds = ['19848247723672', '19848247723673', '19848247723674']; + + foreach ($positionIds as $positionId) { + expect(fn() => $api->flashClosePosition($positionId)) + ->not->toThrow(Exception::class); + } +}); + +it('validates flash close position response structure', function () { + $api = app(FlashClosePositionRequestContract::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->flashClosePosition('19848247723672')) + ->not->toThrow(Exception::class); +}); + +it('can handle special characters in position ID', function () { + $api = app(FlashClosePositionRequestContract::class); + + // Test with position ID containing special characters + expect(fn() => $api->flashClosePosition('pos-123_456')) + ->not->toThrow(Exception::class) + ->and(fn() => $api->flashClosePosition('pos.123.456')) + ->not->toThrow(Exception::class); +}); + +it('validates flash close position with empty string', function () { + $api = app(FlashClosePositionRequestContract::class); + + // This should not throw an exception at the method level + // The API will handle validation + expect(fn() => $api->flashClosePosition('')) + ->not->toThrow(Exception::class); +});