Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ A modern PHP SDK for selected MetaApi services:
- MetaTrader account management
- Provisioning profiles
- Account replicas
- MT4/MT5 demo accounts
- Expert advisors
- User quota management
- Known trading servers
- MetaApi REST terminal API
- CopyFactory
- MetaStats
Expand Down Expand Up @@ -214,14 +218,15 @@ $response = $replicas->createReplica(
'magic' => 123456,
'region' => 'london',
],
transactionId: bin2hex(random_bytes(16))
);

if ($response->shouldRetry()) {
echo "Replica creation accepted. Retry after: " . ($response->retryAfter() ?? 'not specified');
}
```

A transaction ID is auto-generated. Pass your own `transactionId` to retry an accepted request.

Available methods:

- `replicas(string $accountId)`
Expand All @@ -235,6 +240,148 @@ Available methods:
- `generateReplicaCodeSample(string $accountId, string $replicaId, string $platform)`
- `increaseReplicaReliability(string $accountId, string $replicaId)`

## Demo Accounts

```php
$demoAccounts = $metaapi->demoAccounts();
```

Create a MetaTrader 4 demo account:

```php
$response = $demoAccounts->createMT4DemoAccount(
'provisioning-profile-id',
[
'accountType' => 'type',
'balance' => 1000,
'email' => 'user@example.com',
'leverage' => 10,
'name' => 'Test User',
'phone' => '+12345678901',
'serverName' => 'Example-Server',
'keywords' => ['Example Broker Ltd'],
],
);

if ($response->isCreated()) {
echo "Demo account created. Login: " . $response->body()['login'];
}

if ($response->shouldRetry()) {
echo "Request accepted. Retry after: " . ($response->retryAfter() ?? 'not specified');
}
```

A transaction ID is auto-generated for you. You can also pass your own if you need to retry an accepted request:

```php
$response = $demoAccounts->createMT4DemoAccount(
'provisioning-profile-id',
[...],
transactionId: 'my-custom-32-char-transaction-id'
);
```

Create a MetaTrader 5 demo account:

```php
$response = $demoAccounts->createMT5DemoAccount(
'provisioning-profile-id',
[
'accountType' => 'type',
'balance' => 1000,
'email' => 'user@example.com',
'leverage' => 10,
'name' => 'Test User',
'phone' => '+12345678901',
'serverName' => 'Example-Server',
'keywords' => ['Example Broker Ltd'],
],
);
```

Both methods return `ActionResponse` — the same type used by `accounts()->create()`.

## Expert Advisors

```php
$eas = $metaapi->expertAdvisors();
```

```php
// List all expert advisors on an account
$advisors = $eas->expertAdvisors('account-id');

// Read a specific expert advisor
$advisor = $eas->expertAdvisor('account-id', 'expert-id');

// Update or create an expert advisor
$eas->updateExpertAdvisor('account-id', 'expert-id', [
'symbol' => 'EURUSD',
'period' => '1H',
'preset' => 'base64-encoded-preset',
]);

// Upload an expert advisor file
$eas->uploadExpertAdvisorFile('account-id', 'expert-id', '/path/to/expert.ex5');

// Delete an expert advisor
$eas->deleteExpertAdvisor('account-id', 'expert-id');
```

Available methods:

- `expertAdvisors(string $accountId)`
- `expertAdvisor(string $accountId, string $expertId)`
- `updateExpertAdvisor(string $accountId, string $expertId, array $data)`
- `uploadExpertAdvisorFile(string $accountId, string $expertId, string $filePath)`
- `deleteExpertAdvisor(string $accountId, string $expertId)`

## Quota

```php
$quota = $metaapi->quotas();
```

```php
// Get user quota and usage for all regions
$quotas = $quota->quotas();

// Get quota update requests
$updateRequests = $quota->quotaUpdateRequests();

// Request a region quota update
$quota->requestRegionQuotaUpdate('vint-hill', [
'maxAccounts' => 150,
'maxDeployedG1Accounts' => 25,
'maxDeployedG2Accounts' => 120,
'maxDeployedCopyFactoryAccounts' => 120,
'maxDedicatedIpv4' => 100,
'justification' => 'quota update request justification message',
]);
```

Available methods:

- `quotas()`
- `quotaUpdateRequests()`
- `requestRegionQuotaUpdate(string $region, array $data)`

## Mt Servers

```php
$mtServers = $metaapi->mtServers();
```

```php
// Search known trading servers by MT version
$servers = $mtServers->knownTradingServers(5, 'icmarketssc');
```

Available methods:

- `knownTradingServers(int $version, string $query)`

## CopyFactory

```php
Expand Down
24 changes: 24 additions & 0 deletions src/MetaApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
use GuzzleHttp\ClientInterface;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\Account;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\AccountReplica;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\DemoAccount;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\ExpertAdvisor;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\MtServer;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\ProvisioningProfile;
use Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement\Quota;

class MetaApiClient
{
Expand Down Expand Up @@ -49,6 +53,26 @@ public function accountReplicas(): AccountReplica
return new AccountReplica($this->accountHttp);
}

public function demoAccounts(): DemoAccount
{
return new DemoAccount($this->accountHttp);
}

public function expertAdvisors(): ExpertAdvisor
{
return new ExpertAdvisor($this->accountHttp);
}

public function quotas(): Quota
{
return new Quota($this->accountHttp);
}

public function mtServers(): MtServer
{
return new MtServer($this->accountHttp);
}

public function copyFactory(?string $region = null, ?string $serverUrl = null): CopyFactory
{
return new CopyFactory(
Expand Down
2 changes: 1 addition & 1 deletion src/Resources/AccountManagement/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function createConfigurationLink(string $accountId, ?int $ttlInDays = nul

private function transactionHeader(?string $transactionId): array
{
return $transactionId ? ['transaction-id' => $transactionId] : [];
return ['transaction-id' => $transactionId ?? bin2hex(random_bytes(16))];
}

private function apiVersionHeader(?int $apiVersion): array
Expand Down
7 changes: 6 additions & 1 deletion src/Resources/AccountManagement/AccountReplica.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ public function createReplica(string $accountId, array $data, ?string $transacti
return $this->http->postAction(
"/users/current/accounts/{$accountId}/replicas",
$data,
$transactionId ? ['transaction-id' => $transactionId] : []
$this->transactionHeader($transactionId)
);
}

private function transactionHeader(?string $transactionId): array
{
return ['transaction-id' => $transactionId ?? bin2hex(random_bytes(16))];
}

public function updateReplica(string $accountId, string $replicaId, array $data): array|string|null
{
return $this->http->put("/users/current/accounts/{$accountId}/replicas/{$replicaId}", $data);
Expand Down
34 changes: 34 additions & 0 deletions src/Resources/AccountManagement/DemoAccount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement;

use Victorycodedev\MetaapiCloudPhpSdk\Http;
use Victorycodedev\MetaapiCloudPhpSdk\Responses\ActionResponse;

class DemoAccount
{
public function __construct(private readonly Http $http) {}

public function createMT4DemoAccount(string $profileId, array $data, ?string $transactionId = null): ActionResponse
{
return $this->http->postAction(
"/users/current/provisioning-profiles/{$profileId}/mt4-demo-accounts",
$data,
$this->transactionHeader($transactionId)
);
}

public function createMT5DemoAccount(string $profileId, array $data, ?string $transactionId = null): ActionResponse
{
return $this->http->postAction(
"/users/current/provisioning-profiles/{$profileId}/mt5-demo-accounts",
$data,
$this->transactionHeader($transactionId)
);
}

private function transactionHeader(?string $transactionId): array
{
return ['transaction-id' => $transactionId ?? bin2hex(random_bytes(16))];
}
}
38 changes: 38 additions & 0 deletions src/Resources/AccountManagement/ExpertAdvisor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement;

use Victorycodedev\MetaapiCloudPhpSdk\Http;

class ExpertAdvisor
{
public function __construct(private readonly Http $http) {}

public function expertAdvisors(string $accountId): array|string|null
{
return $this->http->get("/users/current/accounts/{$accountId}/expert-advisors");
}

public function expertAdvisor(string $accountId, string $expertId): array|string|null
{
return $this->http->get("/users/current/accounts/{$accountId}/expert-advisors/{$expertId}");
}

public function updateExpertAdvisor(string $accountId, string $expertId, array $data): array|string|null
{
return $this->http->put("/users/current/accounts/{$accountId}/expert-advisors/{$expertId}", $data);
}

public function uploadExpertAdvisorFile(string $accountId, string $expertId, string $filePath): array|string|null
{
return $this->http->upload(
"/users/current/accounts/{$accountId}/expert-advisors/{$expertId}/file",
$filePath
);
}

public function deleteExpertAdvisor(string $accountId, string $expertId): array|string|null
{
return $this->http->delete("/users/current/accounts/{$accountId}/expert-advisors/{$expertId}");
}
}
15 changes: 15 additions & 0 deletions src/Resources/AccountManagement/MtServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement;

use Victorycodedev\MetaapiCloudPhpSdk\Http;

class MtServer
{
public function __construct(private readonly Http $http) {}

public function knownTradingServers(int $version, string $query): array|string|null
{
return $this->http->get("/known-mt-servers/{$version}/search", ['query' => $query]);
}
}
25 changes: 25 additions & 0 deletions src/Resources/AccountManagement/Quota.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Victorycodedev\MetaapiCloudPhpSdk\Resources\AccountManagement;

use Victorycodedev\MetaapiCloudPhpSdk\Http;

class Quota
{
public function __construct(private readonly Http $http) {}

public function quotas(): array|string|null
{
return $this->http->get('/users/current/quotas');
}

public function quotaUpdateRequests(): array|string|null
{
return $this->http->get('/users/current/quota-update-requests');
}

public function requestRegionQuotaUpdate(string $region, array $data): array|string|null
{
return $this->http->patch("/users/current/regions/{$region}/quotas", $data);
}
}
24 changes: 24 additions & 0 deletions tests/AccountManagementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@
expect($history[0]['request']->getHeaderLine('transaction-id'))->toBe('12345678901234567890123456789012');
});

it('auto-generates transaction id for account create when not provided', function (): void {
$history = [];
$metaapi = metaApiClientWithHistory([new Response(201, [], '{}')], $history);

$metaapi->accounts()->create(['name' => 'Demo']);

$transactionId = $history[0]['request']->getHeaderLine('transaction-id');
expect($transactionId)->not->toBeEmpty();
expect(strlen($transactionId))->toBe(32);
expect(ctype_xdigit($transactionId))->toBeTrue();
});

it('exposes accepted account creation retry metadata', function (): void {
$metaapi = metaApiClientWithHistory([
new Response(202, ['Retry-After' => '10'], '{"id":"account-id","state":"DRAFT"}'),
Expand Down Expand Up @@ -86,6 +98,18 @@
expect($history[0]['request']->getHeaderLine('transaction-id'))->toBe('transaction-id');
});

it('auto-generates transaction id for replica create when not provided', function (): void {
$history = [];
$metaapi = metaApiClientWithHistory([new Response(201, [], '{}')], $history);

$metaapi->accountReplicas()->createReplica('account-id', ['magic' => 123456]);

$transactionId = $history[0]['request']->getHeaderLine('transaction-id');
expect($transactionId)->not->toBeEmpty();
expect(strlen($transactionId))->toBe(32);
expect(ctype_xdigit($transactionId))->toBeTrue();
});

it('exposes accepted account replica creation retry metadata', function (): void {
$metaapi = metaApiClientWithHistory([
new Response(202, ['Retry-After' => '15'], '{"id":"replica-id","state":"DRAFT"}'),
Expand Down
Loading
Loading