Merge pull request #3 from mahdimsr/future-private

Future private
This commit is contained in:
mahdi mansouri 2025-09-29 03:21:02 +03:30 committed by GitHub
commit 4b74c9e61a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 3244 additions and 52 deletions

395
README.md
View File

@ -1,84 +1,389 @@
# composer package for using bitunix api trading
# Laravel Bitunix API Package
[![Latest Version on Packagist](https://img.shields.io/packagist/v/mahdimsr/laravel-bitunix-api.svg?style=flat-square)](https://packagist.org/packages/mahdimsr/laravel-bitunix-api)
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/mahdimsr/laravel-bitunix-api/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/mahdimsr/laravel-bitunix-api/actions?query=workflow%3Arun-tests+branch%3Amain)
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/mahdimsr/laravel-bitunix-api/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/mahdimsr/laravel-bitunix-api/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/mahdimsr/laravel-bitunix-api.svg?style=flat-square)](https://packagist.org/packages/mahdimsr/laravel-bitunix-api)
This is where your description should go. Limit it to a paragraph or two. Consider adding a small example.
## Support us
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-bitunix-api.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-bitunix-api)
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
A Laravel package for interacting with the Bitunix cryptocurrency exchange API.
## Installation
You can install the package via composer:
```bash
composer require mahdimsr/laravel-bitunix-api
composer require msr/laravel-bitunix-api
```
You can publish and run the migrations with:
## Configuration
```bash
php artisan vendor:publish --tag="laravel-bitunix-api-migrations"
php artisan migrate
### 1. Environment Variables
Add the following variables to your `.env` file:
```env
BITUNIX_API_KEY=your-api-key-here
BITUNIX_API_SECRET=your-api-secret-here
BITUNIX_LANGUAGE=en-US
```
You can publish the config file with:
### 2. Publish Configuration (Optional)
```bash
php artisan vendor:publish --tag="laravel-bitunix-api-config"
php artisan vendor:publish --tag=bitunix-api-config
```
This is the contents of the published config file:
### 3. Verify Configuration
```php
return [
];
```
Optionally, you can publish the views using
Run the configuration check script:
```bash
php artisan vendor:publish --tag="laravel-bitunix-api-views"
php scripts/check-config.php
```
## Usage
### Change Leverage
```php
$laravelBitunixApi = new Msr\LaravelBitunixApi();
echo $laravelBitunixApi->echoPhrase('Hello, Msr!');
use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract;
$api = app(ChangeLeverageRequestContract::class);
$response = $api->changeLeverage('BTCUSDT', 'USDT', 12);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "Leverage changed successfully!";
}
}
```
### Change Margin Mode
```php
use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract;
$api = app(ChangeMarginModeRequestContract::class);
$response = $api->changeMarginMode('BTCUSDT', 'USDT', 'ISOLATION');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "Margin mode changed successfully!";
}
}
```
### Place Order
```php
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
$api = app(PlaceOrderRequestContract::class);
// Basic market order
$response = $api->placeOrder('BTCUSDT', '0.1', 'BUY', 'OPEN', 'MARKET');
// Limit order with take profit and stop loss
$response = $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000', // price
null, // positionId
'GTC', // effect
'order-123', // clientId
false, // reduceOnly
'51000', // tpPrice
'MARK_PRICE', // tpStopType
'LIMIT', // tpOrderType
'51000.1', // tpOrderPrice
'49000', // slPrice
'MARK_PRICE', // slStopType
'LIMIT', // slOrderType
'49000.1' // slOrderPrice
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "Order placed successfully!";
echo "Order ID: " . $data['data']['orderId'];
}
}
```
### 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 Pending Positions
```php
use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract;
$api = app(GetPendingPositionsRequestContract::class);
// Get all pending positions
$response = $api->getPendingPositions();
// Get positions by symbol
$response = $api->getPendingPositions('BTCUSDT');
// Get specific position by ID
$response = $api->getPendingPositions(null, '19848247723672');
// Get positions with both symbol and position ID
$response = $api->getPendingPositions('BTCUSDT', '19848247723672');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "Positions retrieved successfully!";
foreach ($data['data'] as $position) {
echo "Position ID: " . $position['positionId'];
echo "Symbol: " . $position['symbol'];
echo "Side: " . $position['side'];
echo "Unrealized PnL: " . $position['unrealizedPNL'];
}
}
}
```
### Get Single Account
```php
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
$api = app(GetSingleAccountRequestContract::class);
$response = $api->getSingleAccount('USDT');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
$account = $data['data'][0];
echo "Account retrieved successfully!";
echo "Margin Coin: " . $account['marginCoin'];
echo "Available: " . $account['available'];
echo "Frozen: " . $account['frozen'];
echo "Margin: " . $account['margin'];
echo "Transfer: " . $account['transfer'];
echo "Position Mode: " . $account['positionMode'];
echo "Cross Unrealized PnL: " . $account['crossUnrealizedPNL'];
echo "Isolation Unrealized PnL: " . $account['isolationUnrealizedPNL'];
echo "Bonus: " . $account['bonus'];
}
}
```
### 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'];
}
}
```
### Place Position TP/SL Order
```php
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
$api = app(PlacePositionTpSlOrderRequestContract::class);
// Place position TP/SL order with both take profit and stop loss
$response = $api->placePositionTpSlOrder(
'BTCUSDT', // symbol
'111', // positionId
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE' // slStopType
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "Position TP/SL order placed successfully!";
echo "Order ID: " . $data['data']['orderId'];
}
}
```
### Get Future Kline Data
```php
use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract;
$api = app(FutureKLineRequestContract::class);
$response = $api->getFutureKline('BTCUSDT', '1h', 100);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
// Process kline data
}
```
## API Methods
### Account Management
- `changeLeverage(string $symbol, string $marginCoin, int $leverage)` - Change leverage
- `changeMarginMode(string $symbol, string $marginCoin, string $marginMode)` - Change margin mode
- `getSingleAccount(string $marginCoin)` - Get account details for specific margin coin
### Trading
- `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
- `placePositionTpSlOrder(...)` - Place position TP/SL order (closes position at market price when triggered)
- `flashClosePosition(string $positionId)` - Flash close position by position ID
### Position Management
- `getPendingPositions(?string $symbol, ?string $positionId)` - Get pending positions with optional filtering
### Market Data
- `getFutureKline(string $symbol, string $interval, int $limit, ?int $startTime, ?int $endTime, string $type)` - Get kline data
## Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| `future_base_uri` | Bitunix API base URI | `https://fapi.bitunix.com/` |
| `api_key` | Your API key | From `BITUNIX_API_KEY` env var |
| `api_secret` | Your API secret | From `BITUNIX_API_SECRET` env var |
| `language` | API language | From `BITUNIX_LANGUAGE` env var or `en-US` |
## Rate Limits
- **Change Leverage**: 10 req/sec/uid
- **Change Margin Mode**: 10 req/sec/uid
- **Get Single Account**: 10 req/sec/uid
- **Place Order**: 10 req/sec/uid
- **Place TP/SL Order**: 10 req/sec/uid
- **Place Position TP/SL Order**: 10 req/sec/uid
- **Flash Close Position**: 5 req/sec/uid
- **Get Pending Positions**: 10 req/sec/uid
## Error Handling
All methods return a `ResponseInterface` object. Check the response status and parse the JSON response:
```php
$response = $api->changeLeverage('BTCUSDT', 'USDT', 12);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
// Success
echo "Operation successful: " . $data['msg'];
} else {
// API Error
echo "API Error: " . $data['msg'];
}
} else {
// HTTP Error
echo "HTTP Error: " . $response->getStatusCode();
}
```
## Testing
Run the test suite:
```bash
composer test
vendor/bin/pest
```
## Changelog
Run specific tests:
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
```bash
vendor/bin/pest tests/ChangeLeverageTest.php
vendor/bin/pest tests/ChangeMarginModeTest.php
vendor/bin/pest tests/GetSingleAccountTest.php
vendor/bin/pest tests/PlaceOrderTest.php
vendor/bin/pest tests/PlaceTpSlOrderTest.php
vendor/bin/pest tests/PlacePositionTpSlOrderTest.php
vendor/bin/pest tests/FlashClosePositionTest.php
vendor/bin/pest tests/GetPendingPositionsTest.php
vendor/bin/pest tests/HeaderTest.php
```
## Contributing
## Examples
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
See the `examples/` directory for complete usage examples:
## Security Vulnerabilities
- `ChangeLeverageExample.php`
- `ChangeMarginModeExample.php`
- `GetSingleAccountExample.php`
- `PlaceOrderExample.php`
- `PlaceTpSlOrderExample.php`
- `PlacePositionTpSlOrderExample.php`
- `FlashClosePositionExample.php`
- `GetPendingPositionsExample.php`
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
## Troubleshooting
## Credits
### API credentials not loading from .env
- [mahdi mansouri](https://github.com/mahdimsr)
- [All Contributors](../../contributors)
1. Make sure your `.env` file is in the project root
2. Check that the variable names match exactly (case-sensitive)
3. Restart your application/server after changing .env
4. Clear config cache: `php artisan config:clear`
### Signature generation fails
1. Verify API key and secret are correct
2. Check that the credentials have the necessary permissions
3. Ensure your system time is synchronized
## Security
- Never commit API credentials to version control
- Use environment variables for all sensitive data
- Restrict API key permissions to minimum required
- Regularly rotate API keys
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
MIT License. See LICENSE file for details.
## Support
For issues and questions, please create an issue on the GitHub repository.

View File

@ -18,7 +18,7 @@
"require": {
"php": "^8.4",
"spatie/laravel-package-tools": "^1.16",
"illuminate/contracts": "^11.0||^12.0"
"illuminate/contracts": "^11.0||^12.0||^10.0"
},
"require-dev": {
"laravel/pint": "^1.14",
@ -69,6 +69,7 @@
}
}
},
"minimum-stability": "dev",
"version": "1.0.0",
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Example usage of Change Margin Mode functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to change margin mode for a trading pair on Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract;
// 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(ChangeMarginModeRequestContract::class);
// Change margin mode for BTCUSDT pair
$symbol = 'BTCUSDT';
$marginCoin = 'USDT';
$marginMode = 'ISOLATION'; // or 'CROSS'
echo "Changing margin mode for {$symbol} to {$marginMode}...\n";
$response = $api->changeMarginMode($symbol, $marginCoin, $marginMode);
// Check response status
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Margin mode changed successfully!\n";
echo 'Symbol: '.$data['data'][0]['symbol']."\n";
echo 'Margin Coin: '.$data['data'][0]['marginCoin']."\n";
echo 'Margin Mode: '.$data['data'][0]['marginMode']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
} catch (Exception $e) {
echo '❌ Exception: '.$e->getMessage()."\n";
}
/**
* Available Margin Modes:
* - ISOLATION: Isolated margin mode
* - CROSS: Cross margin mode
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

View File

@ -0,0 +1,117 @@
<?php
/**
* Example usage of Flash Close Position functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to flash close positions on Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract;
// 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(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
*/

View File

@ -0,0 +1,181 @@
<?php
/**
* Example usage of Get Pending Positions functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to get pending positions from Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract;
// 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(GetPendingPositionsRequestContract::class);
echo "📊 Get Pending Positions Examples\n\n";
// Example 1: Get all pending positions
echo "1. Getting all pending positions...\n";
$response = $api->getPendingPositions();
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Pending positions retrieved successfully!\n";
echo 'Number of positions: '.count($data['data'])."\n";
foreach ($data['data'] as $position) {
echo ' - Position ID: '.$position['positionId']."\n";
echo ' Symbol: '.$position['symbol']."\n";
echo ' Side: '.$position['side']."\n";
echo ' Quantity: '.$position['qty']."\n";
echo ' Unrealized PnL: '.$position['unrealizedPNL']."\n";
echo ' Margin: '.$position['margin']."\n";
echo ' Leverage: '.$position['leverage']."\n";
echo " ---\n";
}
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 2: Get pending positions by symbol
echo "2. Getting pending positions for BTCUSDT...\n";
$response = $api->getPendingPositions('BTCUSDT');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ BTCUSDT pending positions retrieved successfully!\n";
echo 'Number of BTCUSDT positions: '.count($data['data'])."\n";
foreach ($data['data'] as $position) {
echo ' - Position ID: '.$position['positionId']."\n";
echo ' Entry Value: '.$position['entryValue']."\n";
echo ' Average Open Price: '.$position['avgOpenPrice']."\n";
echo ' Liquidation Price: '.$position['liqPrice']."\n";
echo ' Margin Rate: '.$position['marginRate']."\n";
echo " ---\n";
}
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 3: Get specific position by ID
echo "3. Getting specific position by ID...\n";
$positionId = '19848247723672';
$response = $api->getPendingPositions(null, $positionId);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position {$positionId} retrieved successfully!\n";
if (! empty($data['data'])) {
$position = $data['data'][0];
echo " Position Details:\n";
echo ' Position ID: '.$position['positionId']."\n";
echo ' Symbol: '.$position['symbol']."\n";
echo ' Side: '.$position['side']."\n";
echo ' Quantity: '.$position['qty']."\n";
echo ' Entry Value: '.$position['entryValue']."\n";
echo ' Average Open Price: '.$position['avgOpenPrice']."\n";
echo ' Unrealized PnL: '.$position['unrealizedPNL']."\n";
echo ' Realized PnL: '.$position['realizedPNL']."\n";
echo ' Margin: '.$position['margin']."\n";
echo ' Leverage: '.$position['leverage']."\n";
echo ' Margin Mode: '.$position['marginMode']."\n";
echo ' Position Mode: '.$position['positionMode']."\n";
echo ' Liquidation Price: '.$position['liqPrice']."\n";
echo ' Margin Rate: '.$position['marginRate']."\n";
echo ' Fee: '.$position['fee']."\n";
echo ' Funding: '.$position['funding']."\n";
echo ' Created: '.date('Y-m-d H:i:s', $position['ctime'] / 1000)."\n";
echo ' Modified: '.date('Y-m-d H:i:s', $position['mtime'] / 1000)."\n";
} else {
echo "No position found with ID: {$positionId}\n";
}
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 4: Get positions with both symbol and position ID
echo "4. Getting positions with both symbol and position ID...\n";
$response = $api->getPendingPositions('BTCUSDT', '19848247723672');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Filtered positions retrieved successfully!\n";
echo 'Number of filtered positions: '.count($data['data'])."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
} catch (Exception $e) {
echo '❌ Exception: '.$e->getMessage()."\n";
}
/**
* Get Pending Positions Features:
*
* - Get all pending positions
* - Filter by trading pair (symbol)
* - Filter by position ID
* - Get detailed position information
* - Rate limit: 10 req/sec/uid
*
* Response includes:
* - positionId: Position ID
* - symbol: Trading pair
* - qty: Position amount
* - entryValue: Available amount for positions
* - side: LONG or SHORT
* - marginMode: ISOLATION or CROSS
* - positionMode: ONE_WAY or HEDGE
* - leverage: Leverage value
* - fee: Transaction fees
* - funding: Total funding fee
* - realizedPNL: Realized PnL
* - margin: Locked asset
* - unrealizedPNL: Unrealized PnL
* - liqPrice: Liquidation price
* - marginRate: Margin ratio
* - avgOpenPrice: Average open price
* - ctime: Create timestamp
* - mtime: Latest modify timestamp
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

View File

@ -0,0 +1,156 @@
<?php
/**
* Example usage of Get Single Account functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to get account details from Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
// 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(GetSingleAccountRequestContract::class);
echo "💰 Get Single Account Examples\n\n";
// Example 1: Get USDT account details
echo "1. Getting USDT account details...\n";
$response = $api->getSingleAccount('USDT');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ USDT account details retrieved successfully!\n";
$account = $data['data'][0];
echo "Account Details:\n";
echo ' Margin Coin: '.$account['marginCoin']."\n";
echo ' Available: '.$account['available']."\n";
echo ' Frozen: '.$account['frozen']."\n";
echo ' Margin: '.$account['margin']."\n";
echo ' Transfer: '.$account['transfer']."\n";
echo ' Position Mode: '.$account['positionMode']."\n";
echo ' Cross Unrealized PnL: '.$account['crossUnrealizedPNL']."\n";
echo ' Isolation Unrealized PnL: '.$account['isolationUnrealizedPNL']."\n";
echo ' Bonus: '.$account['bonus']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 2: Get BTC account details
echo "2. Getting BTC account details...\n";
$response = $api->getSingleAccount('BTC');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ BTC account details retrieved successfully!\n";
$account = $data['data'][0];
echo "Account Details:\n";
echo ' Margin Coin: '.$account['marginCoin']."\n";
echo ' Available: '.$account['available']."\n";
echo ' Frozen: '.$account['frozen']."\n";
echo ' Margin: '.$account['margin']."\n";
echo ' Transfer: '.$account['transfer']."\n";
echo ' Position Mode: '.$account['positionMode']."\n";
echo ' Cross Unrealized PnL: '.$account['crossUnrealizedPNL']."\n";
echo ' Isolation Unrealized PnL: '.$account['isolationUnrealizedPNL']."\n";
echo ' Bonus: '.$account['bonus']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
echo "\n";
// Example 3: Get multiple account details
echo "3. Getting multiple account details...\n";
$marginCoins = ['USDT', 'BTC', 'ETH'];
foreach ($marginCoins as $marginCoin) {
echo "Getting {$marginCoin} account details...\n";
$response = $api->getSingleAccount($marginCoin);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
$account = $data['data'][0];
echo "{$marginCoin} account: Available={$account['available']}, Frozen={$account['frozen']}, Margin={$account['margin']}\n";
} else {
echo "❌ Failed to get {$marginCoin} account: ".$data['msg']."\n";
}
} else {
echo "❌ HTTP Error for {$marginCoin}: ".$response->getStatusCode()."\n";
}
}
echo "\n";
// Example 4: Error handling
echo "4. Error handling example...\n";
$invalidMarginCoin = 'INVALID';
$response = $api->getSingleAccount($invalidMarginCoin);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Account details retrieved successfully!\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
echo "This is expected for invalid margin coin\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
} catch (Exception $e) {
echo '❌ Exception: '.$e->getMessage()."\n";
}
/**
* Get Single Account Features:
*
* - Get account details for specific margin coin
* - Rate limit: 10 req/sec/uid
* - Returns comprehensive account information
* - Supports multiple margin coins
*
* Response includes:
* - marginCoin: Margin Coin
* - available: Available quantity in the account
* - frozen: Locked quantity of orders
* - margin: Locked quantity of positions
* - transfer: Maximum transferable amount
* - positionMode: Position mode (ONE_WAY or HEDGE)
* - crossUnrealizedPNL: Unrealized PnL for cross positions
* - isolationUnrealizedPNL: Unrealized PnL for isolation positions
* - bonus: Futures Bonus
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

View File

@ -0,0 +1,167 @@
<?php
/**
* Example usage of Place Order functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to place orders on Bitunix exchange.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
// 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(PlaceOrderRequestContract::class);
echo "🚀 Placing Orders Examples\n\n";
// Example 1: Basic Market Order
echo "1. Placing a basic market order...\n";
$response = $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'MARKET'
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Market order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
}
echo "\n";
// Example 2: Limit Order with Take Profit and Stop Loss
echo "2. Placing a limit order with TP/SL...\n";
$response = $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000', // price
null, // positionId
'GTC', // effect
'order-123', // clientId
false, // reduceOnly
'51000', // tpPrice
'MARK_PRICE', // tpStopType
'LIMIT', // tpOrderType
'51000.1', // tpOrderPrice
'49000', // slPrice
'MARK_PRICE', // slStopType
'LIMIT', // slOrderType
'49000.1' // slOrderPrice
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Limit order with TP/SL placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
echo 'Client ID: '.$data['data']['clientId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
}
echo "\n";
// Example 3: Close Position Order
echo "3. Placing a close position order...\n";
$response = $api->placeOrder(
'BTCUSDT',
'0.1',
'SELL',
'CLOSE',
'MARKET',
null,
'position-123' // positionId required for CLOSE
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Close position order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
}
echo "\n";
// Example 4: Reduce Only Order
echo "4. Placing a reduce only order...\n";
$response = $api->placeOrder(
'BTCUSDT',
'0.05',
'SELL',
'CLOSE',
'MARKET',
null,
'position-123',
null,
null,
true // reduceOnly
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Reduce only order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ API Error: '.$data['msg']."\n";
}
}
} catch (Exception $e) {
echo '❌ Exception: '.$e->getMessage()."\n";
}
/**
* Order Types:
* - LIMIT: Limit orders (requires price)
* - MARKET: Market orders
*
* Order Sides:
* - BUY: Buy order
* - SELL: Sell order
*
* Trade Sides:
* - OPEN: Open a new position
* - CLOSE: Close an existing position (requires positionId)
*
* Effect Types:
* - IOC: Immediate or cancel
* - FOK: Fill or kill
* - GTC: Good till canceled (default)
* - POST_ONLY: POST only
*
* Take Profit / Stop Loss Types:
* - MARK_PRICE: Mark price
* - LAST_PRICE: Last price
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

View File

@ -0,0 +1,254 @@
<?php
/**
* Example usage of Place Position TP/SL Order functionality
*
* This example demonstrates how to use the LaravelBitunixApi package
* to place position TP/SL orders on Bitunix exchange.
*
* Note: When triggered, it will close the position at market price based on the position quantity at that time.
* Each position can only have one Position TP/SL Order.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
// 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(PlacePositionTpSlOrderRequestContract::class);
echo "🎯 Place Position TP/SL Order Examples\n\n";
// Example 1: Place position TP/SL order with both take profit and stop loss
echo "1. Placing position TP/SL order with both TP and SL...\n";
$response = $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE' // slStopType
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position 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 position TP/SL order with take profit only
echo "2. Placing position TP/SL order with take profit only...\n";
$response = $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'MARK_PRICE' // tpStopType
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position 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 position TP/SL order with stop loss only
echo "3. Placing position TP/SL order with stop loss only...\n";
$response = $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
null, // tpPrice
null, // tpStopType
'45000', // slPrice
'MARK_PRICE' // slStopType
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position 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 position TP/SL order with different symbols
echo "4. Placing position TP/SL orders for different symbols...\n";
$symbols = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($symbols as $symbol) {
echo "Placing position TP/SL order for {$symbol}...\n";
$response = $api->placePositionTpSlOrder(
$symbol,
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE'
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "{$symbol} position TP/SL order placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo "❌ Failed to place {$symbol} position TP/SL order: ".$data['msg']."\n";
}
} else {
echo "❌ HTTP Error for {$symbol}: ".$response->getStatusCode()."\n";
}
}
echo "\n";
// Example 5: Place position TP/SL order with different position IDs
echo "5. Placing position TP/SL orders for different position IDs...\n";
$positionIds = ['111', '222', '333'];
foreach ($positionIds as $positionId) {
echo "Placing position TP/SL order for position ID {$positionId}...\n";
$response = $api->placePositionTpSlOrder(
'BTCUSDT',
$positionId,
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE'
);
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: Place position TP/SL order with different stop types
echo "6. Placing position TP/SL orders with different stop types...\n";
$stopTypeCombinations = [
['LAST_PRICE', 'LAST_PRICE'],
['MARK_PRICE', 'MARK_PRICE'],
['LAST_PRICE', 'MARK_PRICE'],
['MARK_PRICE', 'LAST_PRICE'],
];
foreach ($stopTypeCombinations as $index => $combination) {
echo "Placing position TP/SL order with stop types: {$combination[0]}, {$combination[1]}...\n";
$response = $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000',
$combination[0],
'45000',
$combination[1]
);
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position TP/SL order with {$combination[0]}/{$combination[1]} placed successfully!\n";
echo 'Order ID: '.$data['data']['orderId']."\n";
} else {
echo '❌ Failed to place position TP/SL order: '.$data['msg']."\n";
}
} else {
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
}
}
echo "\n";
// Example 7: Error handling
echo "7. Error handling example...\n";
$response = $api->placePositionTpSlOrder('INVALID', '111');
if ($response->getStatusCode() === 200) {
$data = json_decode($response->getBody()->getContents(), true);
if ($data['code'] === 0) {
echo "✅ Position 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 Position TP/SL Order Features:
*
* - Place position TP/SL orders for existing positions
* - Rate limit: 10 req/sec/UID
* - Supports both take profit and stop loss
* - When triggered, closes position at market price
* - Each position can only have one Position TP/SL Order
*
* 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)
*
* Note: At least one of tpPrice or slPrice is required.
*
* Key Differences from regular TP/SL Order:
* - Simpler parameters (no order types, prices, quantities)
* - Automatically closes position at market price when triggered
* - One order per position limit
* - Uses position/place_order endpoint
*
* Environment Variables Required:
*
* BITUNIX_API_KEY=your-api-key
* BITUNIX_API_SECRET=your-api-secret
* BITUNIX_LANGUAGE=en-US
*/

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
*/

81
scripts/check-config.php Normal file
View File

@ -0,0 +1,81 @@
<?php
/**
* Configuration Check Script
*
* This script helps you verify that your Bitunix API configuration is working correctly.
*/
require_once __DIR__.'/../vendor/autoload.php';
use Msr\LaravelBitunixApi\Requests\Header;
echo "🔍 Checking Bitunix API Configuration...\n\n";
// Check if .env file exists
$envFile = __DIR__.'/../.env';
if (! file_exists($envFile)) {
echo "❌ .env file not found. Please create one based on .env.example\n";
exit(1);
}
// Load environment variables
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos($line, '=') !== false && strpos($line, '#') !== 0) {
[$key, $value] = explode('=', $line, 2);
putenv(trim($key).'='.trim($value));
}
}
}
// Check environment variables
$apiKey = getenv('BITUNIX_API_KEY');
$apiSecret = getenv('BITUNIX_API_SECRET');
$language = getenv('BITUNIX_LANGUAGE') ?: 'en-US';
echo "📋 Environment Variables:\n";
echo ' BITUNIX_API_KEY: '.(empty($apiKey) ? '❌ Not set' : '✅ Set ('.substr($apiKey, 0, 8).'...)')."\n";
echo ' BITUNIX_API_SECRET: '.(empty($apiSecret) ? '❌ Not set' : '✅ Set ('.substr($apiSecret, 0, 8).'...)')."\n";
echo ' BITUNIX_LANGUAGE: '.($language)."\n\n";
if (empty($apiKey) || empty($apiSecret)) {
echo "❌ API credentials not configured properly.\n";
echo "Please set BITUNIX_API_KEY and BITUNIX_API_SECRET in your .env file.\n";
exit(1);
}
// Test configuration loading
config([
'bitunix-api.future_base_uri' => 'https://fapi.bitunix.com/',
'bitunix-api.api_key' => $apiKey,
'bitunix-api.api_secret' => $apiSecret,
'bitunix-api.language' => $language,
]);
echo "🔧 Configuration Test:\n";
echo ' Base URI: '.config('bitunix-api.future_base_uri')."\n";
echo ' API Key: '.substr(config('bitunix-api.api_key'), 0, 8)."...\n";
echo ' API Secret: '.substr(config('bitunix-api.api_secret'), 0, 8)."...\n";
echo ' Language: '.config('bitunix-api.language')."\n\n";
// Test header generation
try {
echo "🔐 Testing Header Generation:\n";
$headers = Header::generateHeaders([], '{"test":"value"}');
echo ' API Key: '.$headers['api-key']."\n";
echo ' Sign: '.substr($headers['sign'], 0, 16)."...\n";
echo ' Nonce: '.$headers['nonce']."\n";
echo ' Timestamp: '.$headers['timestamp']."\n";
echo ' Language: '.$headers['language']."\n";
echo ' Content-Type: '.$headers['Content-Type']."\n\n";
echo "✅ Configuration is working correctly!\n";
echo "You can now use the Bitunix API package in your application.\n";
} catch (Exception $e) {
echo '❌ Error generating headers: '.$e->getMessage()."\n";
exit(1);
}

View File

@ -4,11 +4,18 @@ 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\GetPendingPositionsRequestContract;
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
use Msr\LaravelBitunixApi\Requests\Header;
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
use Psr\Http\Message\ResponseInterface;
class LaravelBitunixApi implements ChangeLeverageRequestContract, FutureKLineRequestContract
class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, PlaceOrderRequestContract, PlacePositionTpSlOrderRequestContract, PlaceTpSlOrderRequestContract
{
private Client $publicFutureClient;
@ -21,7 +28,7 @@ class LaravelBitunixApi implements ChangeLeverageRequestContract, FutureKLineReq
protected function getPrivateFutureClient(array $queryParams = [], array $body = []): Client
{
$bodyString = json_encode($body);
$bodyString = count($body) ? json_encode($body) : '';
$headers = Header::generateHeaders($queryParams, $bodyString);
return new Client([
@ -60,4 +67,232 @@ class LaravelBitunixApi implements ChangeLeverageRequestContract, FutureKLineReq
return $response;
}
public function changeMarginMode(string $symbol, string $marginCoin, string $marginMode): ResponseInterface
{
$body = [
'symbol' => $symbol,
'marginCoin' => $marginCoin,
'marginMode' => $marginMode,
];
$response = $this->getPrivateFutureClient([], $body)->post('account/change_margin_mode', [
'json' => $body,
]);
return $response;
}
public function placeOrder(
string $symbol,
string $qty,
string $side,
string $tradeSide,
string $orderType,
?string $price = null,
?string $positionId = null,
?string $effect = null,
?string $clientId = null,
?bool $reduceOnly = null,
?string $tpPrice = null,
?string $tpStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slPrice = null,
?string $slStopType = null,
?string $slOrderType = null,
?string $slOrderPrice = null
): ResponseInterface {
$body = [
'symbol' => $symbol,
'qty' => $qty,
'side' => $side,
'tradeSide' => $tradeSide,
'orderType' => $orderType,
];
// Add optional parameters if provided
if ($price !== null) {
$body['price'] = $price;
}
if ($positionId !== null) {
$body['positionId'] = $positionId;
}
if ($effect !== null) {
$body['effect'] = $effect;
}
if ($clientId !== null) {
$body['clientId'] = $clientId;
}
if ($reduceOnly !== null) {
$body['reduceOnly'] = $reduceOnly;
}
if ($tpPrice !== null) {
$body['tpPrice'] = $tpPrice;
}
if ($tpStopType !== null) {
$body['tpStopType'] = $tpStopType;
}
if ($tpOrderType !== null) {
$body['tpOrderType'] = $tpOrderType;
}
if ($tpOrderPrice !== null) {
$body['tpOrderPrice'] = $tpOrderPrice;
}
if ($slPrice !== null) {
$body['slPrice'] = $slPrice;
}
if ($slStopType !== null) {
$body['slStopType'] = $slStopType;
}
if ($slOrderType !== null) {
$body['slOrderType'] = $slOrderType;
}
if ($slOrderPrice !== null) {
$body['slOrderPrice'] = $slOrderPrice;
}
$response = $this->getPrivateFutureClient([], $body)->post('trade/place_order', [
'json' => $body,
]);
return $response;
}
public function flashClosePosition(string $positionId): ResponseInterface
{
$body = [
'positionId' => $positionId,
];
$response = $this->getPrivateFutureClient([], $body)->post('trade/flash_close_position', [
'json' => $body,
]);
return $response;
}
public function getPendingPositions(?string $symbol = null, ?string $positionId = null): ResponseInterface
{
$queryParams = [];
if ($symbol != null) {
$queryParams['symbol'] = $symbol;
}
if ($positionId != null) {
$queryParams['positionId'] = $positionId;
}
$response = $this->getPrivateFutureClient($queryParams, [])->get('position/get_pending_positions', [
'query' => $queryParams,
]);
return $response;
}
public function getSingleAccount(string $marginCoin): ResponseInterface
{
$queryParams = [
'marginCoin' => $marginCoin,
];
$response = $this->getPrivateFutureClient($queryParams, [])->get('account', [
'query' => $queryParams,
]);
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;
}
public function placePositionTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = 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;
}
$response = $this->getPrivateFutureClient([], $body)->post('tpsl/position/place_order', [
'json' => $body,
]);
return $response;
}
}

View File

@ -4,7 +4,14 @@ 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\GetPendingPositionsRequestContract;
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
@ -31,5 +38,12 @@ class LaravelBitunixApiServiceProvider extends PackageServiceProvider
$this->app->bind(FutureKLineRequestContract::class, LaravelBitunixApi::class);
$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);
$this->app->bind(GetPendingPositionsRequestContract::class, LaravelBitunixApi::class);
$this->app->bind(GetSingleAccountRequestContract::class, LaravelBitunixApi::class);
$this->app->bind(PlaceTpSlOrderRequestContract::class, LaravelBitunixApi::class);
$this->app->bind(PlacePositionTpSlOrderRequestContract::class, LaravelBitunixApi::class);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface ChangeMarginModeRequestContract
{
/**
* Change margin mode for a trading pair
*
* @param string $symbol Trading pair (e.g., 'BTCUSDT')
* @param string $marginCoin Margin coin (e.g., 'USDT')
* @param string $marginMode Margin mode ('ISOLATION' or 'CROSS')
*/
public function changeMarginMode(string $symbol, string $marginCoin, string $marginMode): ResponseInterface;
}

View File

@ -0,0 +1,15 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface FlashClosePositionRequestContract
{
/**
* Flash close position by position ID
*
* @param string $positionId Position ID
*/
public function flashClosePosition(string $positionId): ResponseInterface;
}

View File

@ -0,0 +1,16 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface GetPendingPositionsRequestContract
{
/**
* Get pending positions
*
* @param string|null $symbol Trading pair (optional)
* @param string|null $positionId Position ID (optional)
*/
public function getPendingPositions(?string $symbol = null, ?string $positionId = null): ResponseInterface;
}

View File

@ -0,0 +1,15 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface GetSingleAccountRequestContract
{
/**
* Get account details with the given margin coin
*
* @param string $marginCoin Margin coin (e.g., 'USDT')
*/
public function getSingleAccount(string $marginCoin): ResponseInterface;
}

View File

@ -21,6 +21,7 @@ class Header
/**
* Convert sorted parameters to string format
* Example: ["id" => "1", "uid" => "200"] becomes "id1uid200"
* According to Bitunix documentation: String queryParams = "id1uid200"
*/
public static function digestQueryParameters(array $parameters): string
{
@ -78,8 +79,12 @@ class Header
// Step 2: Remove all spaces from body (already done if JSON encoded properly)
$bodyString = trim($body);
// Step 3: Create digest: SHA256(nonce + timestamp + api-key + queryParams + body)
$digestInput = $nonce.$timestamp.$apiKey.$queryParamsString.$bodyString;
// Step 3: Create digest: SHA256(nonce + timestamp + api-key + queryParams + body (if not empty))
if (strlen($bodyString) == 0) {
$digestInput = $nonce.$timestamp.$apiKey.$queryParamsString;
} else {
$digestInput = $nonce.$timestamp.$apiKey.$queryParamsString.$bodyString;
}
$digest = hash('sha256', $digestInput);
// Step 4: Create sign: SHA256(digest + secretKey)

View File

@ -0,0 +1,51 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface PlaceOrderRequestContract
{
/**
* Place a new order
*
* @param string $symbol Trading pair (e.g., 'BTCUSDT')
* @param string $qty Amount (base coin)
* @param string $side Order direction ('BUY' or 'SELL')
* @param string $tradeSide Direction ('OPEN' or 'CLOSE')
* @param string $orderType Order type ('LIMIT' or 'MARKET')
* @param string|null $price Price of the order (required for LIMIT orders)
* @param string|null $positionId Position ID (required when tradeSide is 'CLOSE')
* @param string|null $effect Order expiration date
* @param string|null $clientId Customize order ID
* @param bool|null $reduceOnly Whether to just reduce the position
* @param string|null $tpPrice Take profit trigger price
* @param string|null $tpStopType Take profit trigger type
* @param string|null $tpOrderType Take profit trigger place order type
* @param string|null $tpOrderPrice Take profit trigger place order price
* @param string|null $slPrice Stop loss trigger price
* @param string|null $slStopType Stop loss trigger type
* @param string|null $slOrderType Stop loss trigger place order type
* @param string|null $slOrderPrice Stop loss trigger place order price
*/
public function placeOrder(
string $symbol,
string $qty,
string $side,
string $tradeSide,
string $orderType,
?string $price = null,
?string $positionId = null,
?string $effect = null,
?string $clientId = null,
?bool $reduceOnly = null,
?string $tpPrice = null,
?string $tpStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slPrice = null,
?string $slStopType = null,
?string $slOrderType = null,
?string $slOrderPrice = null
): ResponseInterface;
}

View File

@ -0,0 +1,29 @@
<?php
namespace Msr\LaravelBitunixApi\Requests;
use Psr\Http\Message\ResponseInterface;
interface PlacePositionTpSlOrderRequestContract
{
/**
* Place Position TP/SL Order
* When triggered, it will close the position at market price based on the position quantity at that time.
* Each position can only have one Position 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)
*/
public function placePositionTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = null
): ResponseInterface;
}

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,96 @@
<?php
use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract;
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 change margin mode successfully', function () {
$api = app(ChangeMarginModeRequestContract::class);
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'ISOLATION'))
->not->toThrow(Exception::class)
->and(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'CROSS'))
->not->toThrow(Exception::class);
});
it('validates required parameters for change margin mode', function () {
$api = app(ChangeMarginModeRequestContract::class);
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'ISOLATION'))
->not->toThrow(Exception::class)
->and(fn () => $api->changeMarginMode('ETHUSDT', 'USDT', 'CROSS'))
->not->toThrow(Exception::class);
});
it('handles different margin modes correctly', function () {
$api = app(ChangeMarginModeRequestContract::class);
$marginModes = ['ISOLATION', 'CROSS'];
foreach ($marginModes as $mode) {
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', $mode))
->not->toThrow(Exception::class);
}
});
it('validates margin mode parameter values', function () {
$api = app(ChangeMarginModeRequestContract::class);
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'ISOLATION'))
->not->toThrow(Exception::class)
->and(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'CROSS'))
->not->toThrow(Exception::class);
});
it('can handle different trading pairs', function () {
$api = app(ChangeMarginModeRequestContract::class);
$tradingPairs = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($tradingPairs as $symbol) {
expect(fn () => $api->changeMarginMode($symbol, 'USDT', 'ISOLATION'))
->not->toThrow(Exception::class);
}
});
it('can handle different margin coins', function () {
$api = app(ChangeMarginModeRequestContract::class);
$marginCoins = ['USDT', 'BTC', 'ETH'];
foreach ($marginCoins as $coin) {
expect(fn () => $api->changeMarginMode('BTCUSDT', $coin, 'ISOLATION'))
->not->toThrow(Exception::class);
}
});
it('validates margin mode constants', function () {
$api = app(ChangeMarginModeRequestContract::class);
$validModes = ['ISOLATION', 'CROSS'];
foreach ($validModes as $mode) {
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', $mode))
->not->toThrow(Exception::class);
}
});
it('handles edge cases for margin mode', function () {
$api = app(ChangeMarginModeRequestContract::class);
expect(fn () => $api->changeMarginMode('BTCUSDT', 'USDT', 'ISOLATION'))
->not->toThrow(Exception::class)
->and(fn () => $api->changeMarginMode('ETHUSDT', 'USDT', 'CROSS'))
->not->toThrow(Exception::class);
});

View File

@ -0,0 +1,111 @@
<?php
use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract;
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 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);
});

View File

@ -0,0 +1,149 @@
<?php
use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract;
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 get all pending positions', function () {
$api = app(GetPendingPositionsRequestContract::class);
expect(fn () => $api->getPendingPositions())
->not->toThrow(Exception::class);
});
it('can get pending positions by symbol', function () {
$api = app(GetPendingPositionsRequestContract::class);
expect(fn () => $api->getPendingPositions('BTCUSDT'))
->not->toThrow(Exception::class);
});
it('can get pending positions by position ID', function () {
$api = app(GetPendingPositionsRequestContract::class);
expect(fn () => $api->getPendingPositions(null, '19848247723672'))
->not->toThrow(Exception::class);
});
it('can get pending positions with both symbol and position ID', function () {
$api = app(GetPendingPositionsRequestContract::class);
expect(fn () => $api->getPendingPositions('BTCUSDT', '19848247723672'))
->not->toThrow(Exception::class);
});
it('validates required parameters for get pending positions', function () {
$api = app(GetPendingPositionsRequestContract::class);
// Test without parameters
expect(fn () => $api->getPendingPositions())
->not->toThrow(Exception::class);
// Test with symbol only
expect(fn () => $api->getPendingPositions('BTCUSDT'))
->not->toThrow(Exception::class);
// Test with position ID only
expect(fn () => $api->getPendingPositions(null, '19848247723672'))
->not->toThrow(Exception::class);
});
it('can handle different trading pairs', function () {
$api = app(GetPendingPositionsRequestContract::class);
$tradingPairs = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($tradingPairs as $symbol) {
expect(fn () => $api->getPendingPositions($symbol))
->not->toThrow(Exception::class);
}
});
it('can handle different position ID formats', function () {
$api = app(GetPendingPositionsRequestContract::class);
$positionIds = [
'19848247723672',
'123456789',
'987654321',
'position-123',
'pos_456',
];
foreach ($positionIds as $positionId) {
expect(fn () => $api->getPendingPositions(null, $positionId))
->not->toThrow(Exception::class);
}
});
it('validates get pending positions method exists', function () {
$api = app(GetPendingPositionsRequestContract::class);
expect(method_exists($api, 'getPendingPositions'))->toBeTrue();
});
it('can handle edge cases for parameters', function () {
$api = app(GetPendingPositionsRequestContract::class);
// Test with empty string symbol
expect(fn () => $api->getPendingPositions(''))
->not->toThrow(Exception::class);
// Test with empty string position ID
expect(fn () => $api->getPendingPositions(null, ''))
->not->toThrow(Exception::class);
});
it('can handle special characters in parameters', function () {
$api = app(GetPendingPositionsRequestContract::class);
// Test with special characters in symbol
expect(fn () => $api->getPendingPositions('BTC-USDT'))
->not->toThrow(Exception::class);
// Test with special characters in position ID
expect(fn () => $api->getPendingPositions(null, 'pos-123_456'))
->not->toThrow(Exception::class);
});
it('validates get pending positions response structure', function () {
$api = app(GetPendingPositionsRequestContract::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->getPendingPositions())
->not->toThrow(Exception::class);
});
it('can handle multiple get pending positions calls', function () {
$api = app(GetPendingPositionsRequestContract::class);
$symbols = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($symbols as $symbol) {
expect(fn () => $api->getPendingPositions($symbol))
->not->toThrow(Exception::class);
}
});
it('can handle combination of symbol and position ID', function () {
$api = app(GetPendingPositionsRequestContract::class);
$combinations = [
['BTCUSDT', '19848247723672'],
['ETHUSDT', '19848247723673'],
['ADAUSDT', '19848247723674'],
];
foreach ($combinations as [$symbol, $positionId]) {
expect(fn () => $api->getPendingPositions($symbol, $positionId))
->not->toThrow(Exception::class);
}
});

View File

@ -0,0 +1,113 @@
<?php
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
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 get single account successfully', function () {
$api = app(GetSingleAccountRequestContract::class);
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
});
it('validates required margin coin parameter', function () {
$api = app(GetSingleAccountRequestContract::class);
// Test with valid margin coin
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
// Test with different margin coins
expect(fn () => $api->getSingleAccount('BTC'))
->not->toThrow(Exception::class);
});
it('can handle different margin coins', function () {
$api = app(GetSingleAccountRequestContract::class);
$marginCoins = ['USDT', 'BTC', 'ETH', 'BNB', 'ADA'];
foreach ($marginCoins as $marginCoin) {
expect(fn () => $api->getSingleAccount($marginCoin))
->not->toThrow(Exception::class);
}
});
it('validates get single account method exists', function () {
$api = app(GetSingleAccountRequestContract::class);
expect(method_exists($api, 'getSingleAccount'))->toBeTrue();
});
it('can handle edge cases for margin coin', function () {
$api = app(GetSingleAccountRequestContract::class);
// Test with uppercase margin coin
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
// Test with lowercase margin coin
expect(fn () => $api->getSingleAccount('usdt'))
->not->toThrow(Exception::class);
});
it('validates get single account response structure', function () {
$api = app(GetSingleAccountRequestContract::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->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
});
it('can handle multiple get single account calls', function () {
$api = app(GetSingleAccountRequestContract::class);
$marginCoins = ['USDT', 'BTC', 'ETH'];
foreach ($marginCoins as $marginCoin) {
expect(fn () => $api->getSingleAccount($marginCoin))
->not->toThrow(Exception::class);
}
});
it('validates margin coin parameter type', function () {
$api = app(GetSingleAccountRequestContract::class);
// Test with string margin coin
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
// Test with different string formats
expect(fn () => $api->getSingleAccount('BTC'))
->not->toThrow(Exception::class);
});
it('can handle special characters in margin coin', function () {
$api = app(GetSingleAccountRequestContract::class);
// Test with margin coin containing special characters
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
// Test with margin coin containing numbers
expect(fn () => $api->getSingleAccount('USDT'))
->not->toThrow(Exception::class);
});
it('validates get single account with empty string', function () {
$api = app(GetSingleAccountRequestContract::class);
// This should not throw an exception at the method level
// The API will handle validation
expect(fn () => $api->getSingleAccount(''))
->not->toThrow(Exception::class);
});

237
tests/PlaceOrderTest.php Normal file
View File

@ -0,0 +1,237 @@
<?php
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
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 a basic market order', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'MARKET'
))->not->toThrow(Exception::class);
});
it('can place a limit order with price', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000'
))->not->toThrow(Exception::class);
});
it('can place an order with all optional parameters', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000',
'12345',
'GTC',
'custom-client-id',
false,
'51000',
'MARK_PRICE',
'LIMIT',
'51000.1',
'49000',
'MARK_PRICE',
'LIMIT',
'49000.1'
))->not->toThrow(Exception::class);
});
it('can place a close position order', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'SELL',
'CLOSE',
'MARKET',
null,
'position-123'
))->not->toThrow(Exception::class);
});
it('validates required parameters for place order', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder('BTCUSDT', '0.1', 'BUY', 'OPEN', 'MARKET'))
->not->toThrow(Exception::class)
->and(fn () => $api->placeOrder('BTCUSDT', '0.1', 'SELL', 'OPEN', 'MARKET'))
->not->toThrow(Exception::class);
});
it('handles different order types correctly', function () {
$api = app(PlaceOrderRequestContract::class);
$orderTypes = ['LIMIT', 'MARKET'];
foreach ($orderTypes as $orderType) {
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
$orderType,
$orderType === 'LIMIT' ? '50000' : null
))->not->toThrow(Exception::class);
}
});
it('handles different trade sides correctly', function () {
$api = app(PlaceOrderRequestContract::class);
$tradeSides = ['OPEN', 'CLOSE'];
foreach ($tradeSides as $tradeSide) {
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
$tradeSide,
'MARKET',
null,
$tradeSide === 'CLOSE' ? 'position-123' : null
))->not->toThrow(Exception::class);
}
});
it('can handle different trading pairs', function () {
$api = app(PlaceOrderRequestContract::class);
$tradingPairs = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($tradingPairs as $symbol) {
expect(fn () => $api->placeOrder($symbol, '0.1', 'BUY', 'OPEN', 'MARKET'))
->not->toThrow(Exception::class);
}
});
it('validates order side parameter values', function () {
$api = app(PlaceOrderRequestContract::class);
$sides = ['BUY', 'SELL'];
foreach ($sides as $side) {
expect(fn () => $api->placeOrder('BTCUSDT', '0.1', $side, 'OPEN', 'MARKET'))
->not->toThrow(Exception::class);
}
});
it('validates trade side parameter values', function () {
$api = app(PlaceOrderRequestContract::class);
$tradeSides = ['OPEN', 'CLOSE'];
foreach ($tradeSides as $tradeSide) {
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
$tradeSide,
'MARKET',
null,
$tradeSide === 'CLOSE' ? 'position-123' : null
))->not->toThrow(Exception::class);
}
});
it('can handle take profit and stop loss parameters', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000',
null,
'GTC',
null,
false,
'51000',
'MARK_PRICE',
'LIMIT',
'51000.1',
'49000',
'MARK_PRICE',
'LIMIT',
'49000.1'
))->not->toThrow(Exception::class);
});
it('can handle reduce only orders', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'SELL',
'CLOSE',
'MARKET',
null,
'position-123',
null,
null,
true
))->not->toThrow(Exception::class);
});
it('can handle custom client ID', function () {
$api = app(PlaceOrderRequestContract::class);
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'MARKET',
null,
null,
null,
'custom-order-123'
))->not->toThrow(Exception::class);
});
it('can handle different effect types', function () {
$api = app(PlaceOrderRequestContract::class);
$effects = ['IOC', 'FOK', 'GTC', 'POST_ONLY'];
foreach ($effects as $effect) {
expect(fn () => $api->placeOrder(
'BTCUSDT',
'0.1',
'BUY',
'OPEN',
'LIMIT',
'50000',
null,
$effect
))->not->toThrow(Exception::class);
}
});

View File

@ -0,0 +1,223 @@
<?php
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
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 position TP/SL order with required parameters only', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
});
it('can place position TP/SL order with take profit only', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE' // tpStopType
))->not->toThrow(Exception::class);
});
it('can place position TP/SL order with stop loss only', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
null, // tpPrice
null, // tpStopType
'45000', // slPrice
'LAST_PRICE' // slStopType
))->not->toThrow(Exception::class);
});
it('can place position TP/SL order with both take profit and stop loss', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000', // tpPrice
'LAST_PRICE', // tpStopType
'45000', // slPrice
'LAST_PRICE' // slStopType
))->not->toThrow(Exception::class);
});
it('validates place position TP/SL order method exists', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
expect(method_exists($api, 'placePositionTpSlOrder'))->toBeTrue();
});
it('can handle different symbol formats', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
$symbols = ['BTCUSDT', 'ETHUSDT', 'ADAUSDT'];
foreach ($symbols as $symbol) {
expect(fn () => $api->placePositionTpSlOrder($symbol, '111'))
->not->toThrow(Exception::class);
}
});
it('can handle different position ID formats', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
$positionIds = ['111', 'position-123', '19848247723672'];
foreach ($positionIds as $positionId) {
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', $positionId))
->not->toThrow(Exception::class);
}
});
it('can handle different stop types', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
$stopTypes = ['LAST_PRICE', 'MARK_PRICE'];
foreach ($stopTypes as $stopType) {
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000',
$stopType,
'45000',
$stopType
))->not->toThrow(Exception::class);
}
});
it('can handle different price formats', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
$prices = ['50000', '50000.1', '50000.01', '50000.001'];
foreach ($prices as $price) {
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
$price,
'LAST_PRICE',
'45000',
'LAST_PRICE'
))->not->toThrow(Exception::class);
}
});
it('can handle multiple place position TP/SL order calls', function () {
$api = app(PlacePositionTpSlOrderRequestContract::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'],
];
foreach ($calls as $params) {
expect(fn () => $api->placePositionTpSlOrder(...$params))
->not->toThrow(Exception::class);
}
});
it('validates place position TP/SL order response structure', function () {
$api = app(PlacePositionTpSlOrderRequestContract::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->placePositionTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
});
it('can handle edge cases for position TP/SL order', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
// Test with minimum required parameters
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111'))
->not->toThrow(Exception::class);
// Test with all parameters
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000',
'LAST_PRICE',
'45000',
'LAST_PRICE'
))->not->toThrow(Exception::class);
});
it('can handle special characters in parameters', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
// Test with special characters in position ID
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', 'pos-123-abc'))
->not->toThrow(Exception::class);
// Test with decimal prices
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000.123',
'LAST_PRICE',
'45000.456',
'LAST_PRICE'
))->not->toThrow(Exception::class);
});
it('can handle different combinations of parameters', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
// Test with only TP price
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111', '50000'))
->not->toThrow(Exception::class);
// Test with only SL price
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111', null, null, '45000'))
->not->toThrow(Exception::class);
// Test with only TP stop type
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111', '50000', 'MARK_PRICE'))
->not->toThrow(Exception::class);
// Test with only SL stop type
expect(fn () => $api->placePositionTpSlOrder('BTCUSDT', '111', null, null, '45000', 'MARK_PRICE'))
->not->toThrow(Exception::class);
});
it('can handle position TP/SL order with mixed stop types', function () {
$api = app(PlacePositionTpSlOrderRequestContract::class);
// Test with different stop types for TP and SL
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000',
'LAST_PRICE',
'45000',
'MARK_PRICE'
))->not->toThrow(Exception::class);
// Test with opposite stop types
expect(fn () => $api->placePositionTpSlOrder(
'BTCUSDT',
'111',
'50000',
'MARK_PRICE',
'45000',
'LAST_PRICE'
))->not->toThrow(Exception::class);
});

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);
});