commit
2cdeee9bf2
|
|
@ -20,15 +20,14 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest]
|
||||
php: [8.4, 8.3]
|
||||
laravel: [12.*, 11.*]
|
||||
stability: [prefer-lowest, prefer-stable]
|
||||
stability: [prefer-stable]
|
||||
include:
|
||||
- laravel: 12.*
|
||||
testbench: 10.*
|
||||
- laravel: 11.*
|
||||
testbench: 9.*
|
||||
- laravel: 10.*
|
||||
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,3 +30,4 @@ phpstan.neon
|
|||
testbench.yaml
|
||||
/docs
|
||||
/coverage
|
||||
.env
|
||||
|
|
|
|||
416
README.md
416
README.md
|
|
@ -1,84 +1,406 @@
|
|||
# composer package for using bitunix api trading
|
||||
# Laravel Bitunix API Package
|
||||
|
||||
[](https://packagist.org/packages/mahdimsr/laravel-bitunix-api)
|
||||
[](https://github.com/mahdimsr/laravel-bitunix-api/actions?query=workflow%3Arun-tests+branch%3Amain)
|
||||
[](https://github.com/mahdimsr/laravel-bitunix-api/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
|
||||
[](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
|
||||
|
||||
Run the configuration check script:
|
||||
|
||||
```bash
|
||||
php scripts/check-config.php
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## Using Facade Class
|
||||
|
||||
```php
|
||||
return [
|
||||
];
|
||||
$response = \Msr\LaravelBitunixApi\Facades\LaravelBitunixApi::changeLeverage('BTCUSDT', 'USDT', 12);
|
||||
```
|
||||
|
||||
Optionally, you can publish the views using
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag="laravel-bitunix-api-views"
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Using implemented contracts class
|
||||
|
||||
```php
|
||||
$laravelBitunixApi = new Msr\LaravelBitunixApi();
|
||||
echo $laravelBitunixApi->echoPhrase('Hello, Msr!');
|
||||
$api = new \Msr\LaravelBitunixApi\LaravelBitunixApi();
|
||||
$response = $api->changeLeverage('BTCUSDT', 'USDT', 12);
|
||||
```
|
||||
|
||||
## Using Contracts
|
||||
|
||||
which bind in package service provide (`LaravelBitunixApiServiceProvider`)
|
||||
|
||||
### Change Leverage
|
||||
|
||||
```php
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
// config for Msr/LaravelBitunixApi
|
||||
return [
|
||||
|
||||
'future_base_uri' => 'https://fapi.bitunix.com/',
|
||||
'api_key' => env('BITUNIX_API_KEY'),
|
||||
'api_secret' => env('BITUNIX_API_SECRET'),
|
||||
'language' => 'en-US',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Example usage of Change Leverage functionality
|
||||
*
|
||||
* This example demonstrates how to use the LaravelBitunixApi package
|
||||
* to change leverage for a trading pair on Bitunix exchange.
|
||||
*/
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract;
|
||||
|
||||
// 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(ChangeLeverageRequestContract::class);
|
||||
|
||||
// Change leverage for BTCUSDT pair
|
||||
$symbol = 'BTCUSDT';
|
||||
$marginCoin = 'USDT';
|
||||
$leverage = 12;
|
||||
|
||||
echo "Changing leverage for {$symbol} to {$leverage}x...\n";
|
||||
|
||||
$response = $api->changeLeverage($symbol, $marginCoin, $leverage);
|
||||
|
||||
// Check response status
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if ($data['code'] === 0) {
|
||||
echo "✅ Leverage changed successfully!\n";
|
||||
echo 'Symbol: '.$data['data'][0]['symbol']."\n";
|
||||
echo 'Margin Coin: '.$data['data'][0]['marginCoin']."\n";
|
||||
echo 'New Leverage: '.$data['data'][0]['leverage']."\n";
|
||||
} else {
|
||||
echo '❌ API Error: '.$data['msg']."\n";
|
||||
}
|
||||
} else {
|
||||
echo '❌ HTTP Error: '.$response->getStatusCode()."\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '❌ Exception: '.$e->getMessage()."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment Variables Required:
|
||||
*
|
||||
* BITUNIX_API_KEY=your-api-key
|
||||
* BITUNIX_API_SECRET=your-api-secret
|
||||
* BITUNIX_LANGUAGE=en-US
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -2,4 +2,297 @@
|
|||
|
||||
namespace Msr\LaravelBitunixApi;
|
||||
|
||||
class 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, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, PlaceOrderRequestContract, PlacePositionTpSlOrderRequestContract, PlaceTpSlOrderRequestContract
|
||||
{
|
||||
private Client $publicFutureClient;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->publicFutureClient = new Client([
|
||||
'base_uri' => config('bitunix-api.future_base_uri').'/api/v1/futures/market/',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getPrivateFutureClient(array $queryParams = [], array $body = []): Client
|
||||
{
|
||||
$bodyString = count($body) ? json_encode($body) : '';
|
||||
$headers = Header::generateHeaders($queryParams, $bodyString);
|
||||
|
||||
return new Client([
|
||||
'base_uri' => config('bitunix-api.future_base_uri').'/api/v1/futures/',
|
||||
'headers' => $headers,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getFutureKline(string $symbol, string $interval, int $limit = 100, ?int $startTime = null, ?int $endTime = null, string $type = 'LAST_PRICE'): ResponseInterface
|
||||
{
|
||||
$response = $this->publicFutureClient->get('kline', [
|
||||
'query' => [
|
||||
'symbol' => $symbol,
|
||||
'interval' => $interval,
|
||||
'limit' => $limit,
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $endTime,
|
||||
'type' => $type,
|
||||
],
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function changeLeverage(string $symbol, string $marginCoin, int $leverage): ResponseInterface
|
||||
{
|
||||
$body = [
|
||||
'symbol' => $symbol,
|
||||
'marginCoin' => $marginCoin,
|
||||
'leverage' => $leverage,
|
||||
];
|
||||
|
||||
$response = $this->getPrivateFutureClient([], $body)->post('account/change_leverage', [
|
||||
'json' => $body,
|
||||
]);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@
|
|||
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;
|
||||
|
||||
|
|
@ -22,4 +31,19 @@ class LaravelBitunixApiServiceProvider extends PackageServiceProvider
|
|||
->hasMigration('create_laravel_bitunix_api_table')
|
||||
->hasCommand(LaravelBitunixApiCommand::class);
|
||||
}
|
||||
|
||||
public function packageRegistered(): void
|
||||
{
|
||||
parent::packageRegistered();
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Msr\LaravelBitunixApi\Requests;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface ChangeLeverageRequestContract
|
||||
{
|
||||
/**
|
||||
* Change leverage for a trading pair
|
||||
*
|
||||
* @param string $symbol Trading pair (e.g., 'BTCUSDT')
|
||||
* @param string $marginCoin Margin coin (e.g., 'USDT')
|
||||
* @param int $leverage Leverage value
|
||||
*/
|
||||
public function changeLeverage(string $symbol, string $marginCoin, int $leverage): ResponseInterface;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Msr\LaravelBitunixApi\Requests;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface FutureKLineRequestContract
|
||||
{
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* interval could be: 1m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d 3d 1w 1M
|
||||
* limit: max is 200
|
||||
* startTime : milliseconds format
|
||||
* endTime : milliseconds format
|
||||
* type could be: LAST_PRICE, MARK_PRICE
|
||||
*/
|
||||
public function getFutureKline(string $symbol,
|
||||
string $interval,
|
||||
int $limit = 100,
|
||||
?int $startTime = null,
|
||||
?int $endTime = null,
|
||||
string $type = 'LAST_PRICE'): ResponseInterface;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace Msr\LaravelBitunixApi\Requests;
|
||||
|
||||
class Header
|
||||
{
|
||||
/**
|
||||
* Sort query parameters in ascending ASCII order by Key
|
||||
*/
|
||||
public static function sortQueryParameters(array $parameters): array
|
||||
{
|
||||
if (empty($parameters)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
ksort($parameters, SORT_STRING);
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (empty($parameters)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$sortedParameters = self::sortQueryParameters($parameters);
|
||||
$result = '';
|
||||
|
||||
foreach ($sortedParameters as $key => $value) {
|
||||
$result .= $key.$value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random 32-bit nonce string
|
||||
*/
|
||||
public static function generateNonce(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16)); // 32 characters
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate current timestamp in milliseconds
|
||||
*/
|
||||
public static function generateTimestamp(): string
|
||||
{
|
||||
return (string) round(microtime(true) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate signature according to Bitunix API documentation
|
||||
*
|
||||
* Steps:
|
||||
* 1. Sort all queryParams in ascending ASCII order by Key
|
||||
* 2. Remove all spaces from body string
|
||||
* 3. Create digest: SHA256(nonce + timestamp + api-key + queryParams + body)
|
||||
* 4. Create sign: SHA256(digest + secretKey)
|
||||
*/
|
||||
public static function generateSignValue(array $queryParams = [], string $body = '', string $nonce = '', string $timestamp = ''): string
|
||||
{
|
||||
$apiKey = config('bitunix-api.api_key');
|
||||
$apiSecret = config('bitunix-api.api_secret');
|
||||
|
||||
if (empty($apiKey) || empty($apiSecret)) {
|
||||
throw new \InvalidArgumentException('API key and secret must be configured');
|
||||
}
|
||||
|
||||
// Step 1: Sort query parameters in ascending ASCII order
|
||||
$queryParamsString = self::digestQueryParameters($queryParams);
|
||||
|
||||
// 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 (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)
|
||||
$signInput = $digest.$apiSecret;
|
||||
$sign = hash('sha256', $signInput);
|
||||
|
||||
return $sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate complete headers for authenticated requests
|
||||
*/
|
||||
public static function generateHeaders(array $queryParams = [], string $body = ''): array
|
||||
{
|
||||
$nonce = self::generateNonce();
|
||||
$timestamp = self::generateTimestamp();
|
||||
$sign = self::generateSignValue($queryParams, $body, $nonce, $timestamp);
|
||||
|
||||
return [
|
||||
'api-key' => config('bitunix-api.api_key'),
|
||||
'sign' => $sign,
|
||||
'nonce' => $nonce,
|
||||
'timestamp' => $timestamp,
|
||||
'language' => config('bitunix-api.language', 'en-US'),
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
use Msr\LaravelBitunixApi\Requests\Header;
|
||||
|
||||
it('sort query parameters', function () {
|
||||
|
||||
$emptyQuery = Header::sortQueryParameters([]);
|
||||
expect($emptyQuery)->toBeEmpty();
|
||||
|
||||
$params = [
|
||||
'z_index' => 'z value',
|
||||
'a_index' => 'a value',
|
||||
'b_index' => 'b value',
|
||||
];
|
||||
$sortedParams = Header::sortQueryParameters($params);
|
||||
expect($sortedParams)
|
||||
->toMatchArray([
|
||||
'a_index' => 'a value',
|
||||
'b_index' => 'b value',
|
||||
'z_index' => 'z value',
|
||||
]);
|
||||
});
|
||||
|
||||
it('get sorted query params as string value', function () {
|
||||
|
||||
$digestedParam = Header::digestQueryParameters([]);
|
||||
expect($digestedParam)->toBeEmpty();
|
||||
|
||||
$params = [
|
||||
'z_index' => 'z_value',
|
||||
'a_index' => 'a_value',
|
||||
'b_index' => 'b_value',
|
||||
];
|
||||
$sortedParams = Header::digestQueryParameters($params);
|
||||
expect($sortedParams)->toEqual('a_indexa_valueb_indexb_valuez_indexz_value');
|
||||
});
|
||||
|
||||
it('get some random string 32 bit', function () {
|
||||
|
||||
$firstRandom = Header::generateNonce();
|
||||
$secondRandom = Header::generateNonce();
|
||||
|
||||
expect($firstRandom)
|
||||
->toBeString()
|
||||
->toHaveLength(32)
|
||||
->not()->toEqual($secondRandom)
|
||||
->not()->toBeNull();
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract;
|
||||
|
||||
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 leverage successfully', function () {
|
||||
$api = app(ChangeLeverageRequestContract::class);
|
||||
// This test will make a real API call if credentials are valid
|
||||
// For testing purposes, we'll just verify the method exists and can be called
|
||||
expect(fn () => $api->changeLeverage('BTCUSDT', 'USDT', 12))
|
||||
->not->toThrow(Exception::class);
|
||||
});
|
||||
|
||||
it('validates required parameters for change leverage', function () {
|
||||
$api = app(ChangeLeverageRequestContract::class);
|
||||
|
||||
expect(fn () => $api->changeLeverage('BTCUSDT', 'USDT', 10))
|
||||
->not->toThrow(Exception::class)
|
||||
->and(fn () => $api->changeLeverage('BTCUSDT', 'USDT', 0))
|
||||
->not->toThrow(Exception::class);
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
});
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
it('can test', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
use Msr\LaravelBitunixApi\Requests\Header;
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'bitunix-api.api_key' => 'test-api-key',
|
||||
'bitunix-api.api_secret' => 'test-secret-key',
|
||||
'bitunix-api.language' => 'en-US',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can sort query parameters in ascending ASCII order', function () {
|
||||
$parameters = [
|
||||
'uid' => '200',
|
||||
'id' => '1',
|
||||
'name' => 'test',
|
||||
];
|
||||
|
||||
$sorted = Header::sortQueryParameters($parameters);
|
||||
|
||||
expect(array_keys($sorted))->toBe(['id', 'name', 'uid']);
|
||||
expect($sorted)->toBe([
|
||||
'id' => '1',
|
||||
'name' => 'test',
|
||||
'uid' => '200',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can digest query parameters to string format', function () {
|
||||
$parameters = [
|
||||
'id' => '1',
|
||||
'uid' => '200',
|
||||
];
|
||||
|
||||
$result = Header::digestQueryParameters($parameters);
|
||||
|
||||
expect($result)->toBe('id1uid200');
|
||||
});
|
||||
|
||||
it('can generate a valid nonce', function () {
|
||||
$nonce = Header::generateNonce();
|
||||
|
||||
expect($nonce)
|
||||
->toBeString()
|
||||
->toHaveLength(32)
|
||||
->toMatch('/^[a-f0-9]+$/');
|
||||
});
|
||||
|
||||
it('can generate timestamp in milliseconds', function () {
|
||||
$timestamp = Header::generateTimestamp();
|
||||
|
||||
expect($timestamp)
|
||||
->toBeString()
|
||||
->toMatch('/^\d{13}$/'); // 13 digits for milliseconds
|
||||
});
|
||||
|
||||
it('can generate signature according to Bitunix documentation', function () {
|
||||
$nonce = '123456';
|
||||
$timestamp = '20241120123045';
|
||||
$queryParams = ['id' => '1', 'uid' => '200'];
|
||||
$body = '{"uid":"2899","arr":[{"id":1,"name":"maple"},{"id":2,"name":"lily"}]}';
|
||||
|
||||
$sign = Header::generateSignValue($queryParams, $body, $nonce, $timestamp);
|
||||
|
||||
expect($sign)
|
||||
->toBeString()
|
||||
->toHaveLength(64) // SHA256 hex length
|
||||
->toMatch('/^[a-f0-9]+$/');
|
||||
});
|
||||
|
||||
it('can generate complete headers for authenticated requests', function () {
|
||||
$queryParams = ['symbol' => 'BTCUSDT'];
|
||||
$body = '{"symbol":"BTCUSDT","marginCoin":"USDT","leverage":12}';
|
||||
|
||||
$headers = Header::generateHeaders($queryParams, $body);
|
||||
|
||||
expect($headers)
|
||||
->toHaveKeys(['api-key', 'sign', 'nonce', 'timestamp', 'language', 'Content-Type'])
|
||||
->and($headers['api-key'])->toBe('test-api-key')
|
||||
->and($headers['sign'])->toBeString()
|
||||
->and($headers['nonce'])->toBeString()
|
||||
->and($headers['timestamp'])->toBeString()
|
||||
->and($headers['language'])->toBe('en-US')
|
||||
->and($headers['Content-Type'])->toBe('application/json');
|
||||
});
|
||||
|
||||
it('throws exception when API credentials are missing', function () {
|
||||
config([
|
||||
'bitunix-api.api_key' => '',
|
||||
'bitunix-api.api_secret' => '',
|
||||
]);
|
||||
|
||||
expect(fn () => Header::generateSignValue([], '', 'nonce', 'timestamp'))
|
||||
->toThrow(InvalidArgumentException::class, 'API key and secret must be configured');
|
||||
});
|
||||
|
||||
it('handles empty query parameters correctly', function () {
|
||||
$result = Header::digestQueryParameters([]);
|
||||
|
||||
expect($result)->toBe('');
|
||||
});
|
||||
|
||||
it('handles empty body correctly', function () {
|
||||
$nonce = '123456';
|
||||
$timestamp = '20241120123045';
|
||||
|
||||
$sign = Header::generateSignValue([], '', $nonce, $timestamp);
|
||||
|
||||
expect($sign)
|
||||
->toBeString()
|
||||
->toHaveLength(64);
|
||||
});
|
||||
|
||||
it('generates consistent signature for same inputs', function () {
|
||||
$nonce = '123456';
|
||||
$timestamp = '20241120123045';
|
||||
$queryParams = ['id' => '1'];
|
||||
$body = '{"test":"value"}';
|
||||
|
||||
$sign1 = Header::generateSignValue($queryParams, $body, $nonce, $timestamp);
|
||||
$sign2 = Header::generateSignValue($queryParams, $body, $nonce, $timestamp);
|
||||
|
||||
expect($sign1)->toBe($sign2);
|
||||
});
|
||||
|
||||
it('generates different signatures for different inputs', function () {
|
||||
$nonce = '123456';
|
||||
$timestamp = '20241120123045';
|
||||
|
||||
$sign1 = Header::generateSignValue(['id' => '1'], '{"test":"value1"}', $nonce, $timestamp);
|
||||
$sign2 = Header::generateSignValue(['id' => '2'], '{"test":"value2"}', $nonce, $timestamp);
|
||||
|
||||
expect($sign1)->not->toBe($sign2);
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract;
|
||||
|
||||
it('check kline request response code', function () {
|
||||
|
||||
$bootedClass = app(FutureKLineRequestContract::class);
|
||||
$response = $bootedClass->getFutureKline('BTCUSDT', '1h', 100, now()->subHours(6)->milliseconds, now()->milliseconds);
|
||||
expect($response->getStatusCode())
|
||||
->toBe(200)
|
||||
->and(json_decode($response->getBody()->getContents()))
|
||||
->toHaveKeys(['code', 'data', 'msg']);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
Loading…
Reference in New Issue