tigusigalpa/coinquant-php

Official PHP/Laravel SDK for the CoinQuant Public API

Maintainers

Package info

github.com/tigusigalpa/coinquant-php

pkg:composer/tigusigalpa/coinquant-php

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-21 15:59 UTC

This package is auto-updated.

Last update: 2026-07-21 16:02:57 UTC


README

CoinQuant PHP/Laravel SDK

PHP & Laravel SDK for the CoinQuant Public API

PHP Version License Laravel API Docs

Installation · Quick start · Laravel · Streaming · Backtesting · API reference · FAQ

Related Projects and Documentation

About

CoinQuant takes a trading idea described in plain English and turns it into a backtestable strategy. This SDK wraps the public API so you don't have to deal with raw cURL, SSE parsing, or polling loops by hand.

Works as a standalone PHP library. In Laravel it registers automatically — ServiceProvider and Facade included.

Features

  • PHP 8.1+ and Laravel 10 / 11 / 12 / 13 — strictly typed, no legacy baggage.
  • Standalone or Laravelnew CoinQuantClient($token) anywhere, or the CoinQuant facade with auto-discovery.
  • All 37 public endpoints — health, chats, strategies, versions, backtests, reports, templates, credits, community.
  • Streaming via generatorsprompt(), streamPrompt(), streamChatMessage() classify events into chat, strategy, report, error, or unknown.
  • One-call backtestscreateBacktestAndWait() submits, polls, and returns results with CSV exports.
  • Schema materializationfinalizeChat() turns an AI-drafted schema into a backtestable strategy version.
  • Actionable exceptionsCoinQuantException carries HTTP status, request_id, machine code, and message.
  • Guzzle under the hood — pass your own configured client for retries, proxies, or tracing.

Installation

composer require tigusigalpa/coinquant-php

Requires PHP 8.1+. Guzzle is pulled in automatically.

Quick start

Create a client and check your balance:

use CoinQuant\CoinQuantClient;

$client = new CoinQuantClient(getenv('COINQUANT_TOKEN'));
$credits = $client->getCredits();

echo "Available credits: {$credits['available_credits_total']}";

Every method returns the decoded data payload as a plain PHP array — standard $response['field'] access.

Authentication

CoinQuant uses a JWT bearer token. To get one:

  1. Open the CoinQuant web app.
  2. Go to Settings → Service Accounts → New key.
  3. Copy the one-time access_token (you won't see it again).

Tokens last 30 days and work on every endpoint except the human-profile surfaces (/v1/me, /v1/billing/*). Keep the token in an environment variable — COINQUANT_TOKEN — and don't commit it.

$client = new CoinQuantClient(getenv('COINQUANT_TOKEN'));

Custom Guzzle client

If you need retries, a proxy, or request logging, pass a pre-configured Guzzle client:

use GuzzleHttp\Client as GuzzleClient;

$http = new GuzzleClient([
    'timeout' => 60,
    'headers' => ['Authorization' => 'Bearer ' . getenv('COINQUANT_TOKEN')],
]);

$client = new CoinQuantClient(getenv('COINQUANT_TOKEN'), $http);

Laravel integration

The package auto-registers on Laravel 10+ — no manual wiring needed. Set your token:

# .env
COINQUANT_TOKEN=your_token_here

Publish the config file if you need to change the base URL:

php artisan vendor:publish --tag=coinquant-config

Then use the facade anywhere — controllers, jobs, commands:

use CoinQuant\Laravel\Facades\CoinQuant;

$credits = CoinQuant::getCredits();
$result  = CoinQuant::prompt('Research BTC volatility this week.');

The client is also bound as a singleton, so you can type-hint it for dependency injection:

use CoinQuant\CoinQuantClient;

public function __construct(private CoinQuantClient $coinquant) {}

On Laravel versions without package auto-discovery, register CoinQuant\Laravel\CoinQuantServiceProvider::class and the CoinQuant alias manually in config/app.php.

Streaming

The AI engine responds over Server-Sent Events. The SDK reads the stream and returns a classified StreamResult:

$result = $client->prompt('Generate a BTCUSDT 1h EMA crossover strategy.');

echo $result->text;   // the assembled text reply
echo $result->type;   // 'chat' | 'strategy' | 'report' | 'error' | 'unknown'

To react to each event as it arrives (e.g. stream tokens to a browser), iterate the generator directly:

use CoinQuant\Streaming\StreamEvent;

foreach ($client->streamPrompt('Explain the RSI indicator.') as $event) {
    if ($event->type === StreamEvent::TYPE_CHAT) {
        echo $event->text; // flush to the client in real time
    }
}

Response classification

CoinQuant decides the response shape, not the caller — a "simple" prompt can come back as a full report. The SDK classifies based on the actual events, in this order:

Priority Condition type
1 Any error event, or HTTP status ≥ 400 error
2 A result event carrying strategy_id, strategy_version_id, or schema strategy
3 Any report_block or report event report
4 Only chunk text events chat
5 None of the above unknown

Schema-only strategies

When the AI returns a strategy schema but no strategy_version_id, it's a blueprint that isn't backtestable yet. Materialize it with one call:

if ($result->type === 'strategy' && $result->strategyVersionId === null && $result->chatId !== null) {
    $strategy = $client->finalizeChat($result->chatId, 'EMA Crossover', 'My first strategy');
    echo "Ready to backtest: {$strategy['latest_version']['id']}";
}

Backtesting

Backtests run asynchronously. createBacktestAndWait() submits the run, polls until it reaches a terminal status (completed, failed, cancelled, error, timeout), and on success returns the results plus both CSV exports:

$outcome = $client->createBacktestAndWait('STRATEGY_VERSION_ID', 900 /* timeout s */, 5 /* poll s */);

echo "Status: {$outcome['detail']['status']}";

if ($outcome['results'] !== null) {
    $metrics = $outcome['results']['metrics'];
    echo "Total Return: {$metrics['Total Return']}%";
    echo "Sharpe:       {$metrics['Sharpe Ratio']}";
    // $outcome['summary_csv'] and $outcome['trades_csv'] hold the raw CSV exports.
}

If you'd rather drive the loop yourself, use createBacktest(), getBacktest(), getBacktestResults(), getBacktestSummaryCsv(), and getBacktestTradesCsv() directly.

Handling errors

Any non-2xx response throws a CoinQuantException with the details you need to debug:

use CoinQuant\Exceptions\CoinQuantException;

try {
    $credits = $client->getCredits();
} catch (CoinQuantException $e) {
    logger()->error('CoinQuant call failed', [
        'status'     => $e->getHttpStatus(),
        'code'       => $e->getErrorCode(),
        'request_id' => $e->getRequestId(),
        'message'    => $e->getMessage(),
    ]);

    if ($e->getHttpStatus() === 402) {
        // Out of credits — top up and retry.
    }
}

Quote the request_id when contacting CoinQuant support — they can trace the exact call.

Complete workflow

From idea to metrics:

// 1. Describe the idea and let the AI draft a strategy.
$res = $client->prompt('Long BTCUSDT when price crosses above the 200 EMA on 1h, exit on cross below.');

// 2. Materialize it if it came back schema-only.
$versionId = $res->strategyVersionId
    ?? $client->finalizeChat($res->chatId, 'EMA 200 Crossover', '')['latest_version']['id'];

// 3. Backtest and wait for the verdict.
$outcome = $client->createBacktestAndWait($versionId, 900, 5);
print_r($outcome['results']['metrics']);

Full API reference

Endpoint Method
GET /health health()
GET /v1/chats listChats($options = [])
POST /v1/chats createChat($data)
GET /v1/chats/{id} getChat($id)
PATCH /v1/chats/{id} updateChat($id, $data)
DELETE /v1/chats/{id} deleteChat($id)
GET /v1/chats/{id}/messages listMessages($id, $options = [])
POST /v1/chats/{id}/messages appendMessage($id, $data)
POST /v1/chats/{id}/messages:stream streamChatMessage($id, $content, $options = [])
POST /v1/prompts/stream streamPrompt($message, $chatId = null, $options = [])
GET /v1/strategies listStrategies($options = [])
POST /v1/strategies createStrategy($data) / finalizeChat(...)
GET /v1/strategies/{id} getStrategy($id)
PATCH /v1/strategies/{id} updateStrategy($id, $data)
GET /v1/strategies/{id}/versions listStrategyVersions($id, $options = [])
GET /v1/strategies/{id}/versions/{vid} getStrategyVersion($id, $vid)
PATCH /v1/strategies/{id}/versions/{vid} updateStrategyVersion($id, $vid, $data)
GET /v1/backtests listBacktests($options = [])
POST /v1/backtests createBacktest($strategyVersionId)
GET /v1/backtests/{id} getBacktest($id)
GET /v1/backtests/{id}/results getBacktestResults($id)
GET /v1/backtests/{id}/exports/summary.csv getBacktestSummaryCsv($id)
GET /v1/backtests/{id}/exports/trades.csv getBacktestTradesCsv($id)
POST /v1/backtests/compare compareBacktests($ids)
POST /v1/backtests/{id}/duplicate duplicateBacktest($id)
GET /v1/reports listReports($options = [])
GET /v1/reports/{id} getReport($id)
GET /v1/templates listTemplates($options = [])
GET /v1/templates/{id} getTemplate($id)
GET /v1/credits getCredits()
GET /v1/credits/usage getCreditUsage($options = [])
GET /v1/credits/transactions listCreditTransactions($options = [])
GET /v1/credits/estimates/tick estimateTickCredits($days, $minutes)
GET /v1/community/leaderboards listLeaderboards($options = [])
GET /v1/community/backtests listCommunityBacktests($options = [])
GET /v1/community/backtests/{id} getCommunityBacktest($id, $include = '')
GET /v1/community/me getCommunityMe($include = '')
GET /v1/community/me/activities listCommunityActivities($options = [])

Examples

Each file in examples/ is a self-contained script:

File What it shows
auth_check.php Verify connectivity and read your credit balance
send_prompt.php Stream a prompt and materialize a schema-only strategy
backtest_poll.php Kick off a backtest and wait for the results
community_leaderboard.php Browse the public leaderboard (no token required)
export COINQUANT_TOKEN=your_token_here
php examples/auth_check.php

FAQ

Do I need a token for everything? No. The health check and community endpoints (leaderboards, backtests) work anonymously. Everything else needs a service-account token.

Why did my "simple" prompt come back as a report? That's expected — CoinQuant decides the response shape. Always branch on $result->type rather than on what you asked for.

How long do tokens last? 30 days. When one expires you'll get a 401; create a new key in Settings → Service Accounts.

A backtest keeps returning 409 on results. The run isn't finished yet. Use createBacktestAndWait() (it polls for you), or keep calling getBacktest() until the status is terminal.

Does it work outside Laravel? Yes. CoinQuantClient is a plain PHP class with no framework dependencies — the Laravel provider and facade are optional.

Can I customize timeouts or add retries? Yes — pass your own configured Guzzle client to the constructor.

Contributing

Issues and pull requests are welcome. Target PHP 8.1+, keep the code strictly typed, run php -l on changed files, and describe your change clearly.

License

MIT License.

Author

Igor Sazonov

CoinQuant · API Reference · PHP package