new-exchange

v2026.09.24

Scaffold a new CCXT exchange integration in TypeScript, following the certified-exchange template. Walks through describe(), required unified methods, parsers, capability flags, sandbox setup, and static fixtures. Use when adding support for an exchange that does not exist yet under ts/src/.

GitHub
安装命令
npx skhub add ccxt/new-exchange
Markdown
SKILL.md

New Exchange Integration

Scaffold a new exchange in ts/src/<id>.ts (REST) and optionally ts/src/pro/<id>.ts (WebSocket).

Read first: wiki/Requirements.md (mandatory unified methods) and CONTRIBUTING.md (transpiler rules). The CCXT root CLAUDE.md is the contributor guide.

Inputs

  • <id>: lowercase exchange id, no separators (e.g. mynewex)
  • <Name>: human-readable name (e.g. My New Exchange)
  • API docs URL(s)
  • Whether the exchange has a testnet/sandbox

Step 1 — Pick a reference exchange

Don't write from scratch. Copy a similar exchange that's already certified and adapt it.

StyleReference
Spot + futures, signed RESTts/src/binance.ts, ts/src/okx.ts
Spot onlyts/src/kraken.ts, ts/src/coinbase.ts
Derivatives focusts/src/bybit.ts, ts/src/hyperliquid.ts
Decentralised / on-chain signingts/src/hyperliquid.ts, ts/src/dydx.ts
WebSocket referencets/src/pro/binance.ts, ts/src/pro/okx.ts

Open the reference next to your new file and pattern-match — never invent new conventions.

Step 2 — Create ts/src/<id>.ts

Skeleton:

import Exchange from './abstract/<id>.js';
import { /* errors needed */ } from './base/errors.js';
import { Precise } from './base/Precise.js';
import type { /* types needed */ } from './base/types.js';

export default class <id> extends Exchange {
    describe (): any {
        return this.deepExtend (super.describe (), {
            'id': '<id>',
            'name': '<Name>',
            'countries': [ 'XX' ],
            'rateLimit': 1000,        // ms between requests
            'version': 'v1',
            'certified': false,
            'pro': false,             // flip to true when pro/<id>.ts exists
            'has': {
                // start everything false; flip to true as you implement
                'CORS': undefined,
                'spot': true,
                'margin': false,
                'swap': false,
                'future': false,
                'option': false,
                'fetchMarkets': true,
                'fetchCurrencies': true,
                'fetchTicker': true,
                'fetchTickers': false,
                'fetchOrderBook': true,
                'fetchTrades': true,
                'fetchOHLCV': false,
                'fetchBalance': true,
                'createOrder': true,
                'cancelOrder': true,
                'fetchOrder': true,
                'fetchOpenOrders': true,
                'fetchOrders': false,
                'fetchClosedOrders': false,
                'fetchMyTrades': true,
                'fetchDeposits': false,
                'fetchWithdrawals': false,
                'withdraw': false,
            },
            'urls': {
                'logo': 'https://...',
                'api': {
                    'public': 'https://api.<id>.com',
                    'private': 'https://api.<id>.com',
                },
                'test': {                              // OPTIONAL — only if testnet exists
                    'public': 'https://testnet.<id>.com',
                    'private': 'https://testnet.<id>.com',
                },
                'www': 'https://<id>.com',
                'doc': [ 'https://docs.<id>.com' ],
                'fees': 'https://<id>.com/fees',
            },
            'api': {
                'public': {
                    'get': [
                        'symbols',
                        'ticker/{pair}',
                        'orderbook/{pair}',
                    ],
                },
                'private': {
                    'get': [ 'account', 'orders' ],
                    'post': [ 'order' ],
                    'delete': [ 'order/{id}' ],
                },
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                // 'password': true,        // for passphrase-based exchanges
                // 'walletAddress': true,   // for on-chain
                // 'privateKey': true,
            },
            'fees': {
                'trading': {
                    'tierBased': false,
                    'percentage': true,
                    'maker': 0.001,
                    'taker': 0.001,
                },
            },
            'precisionMode': /* TICK_SIZE | DECIMAL_PLACES | SIGNIFICANT_DIGITS */,
            'options': {
                // exchange-specific defaults
            },
            'exceptions': {
                'exact': {
                    // 'ERROR_CODE': BadRequest,
                },
                'broad': {
                    // 'invalid signature': AuthenticationError,
                },
            },
        });
    }

    // implement the unified methods you flipped on in `has`
    async fetchMarkets (params = {}): Promise<Market[]> { /* ... */ }
    parseMarket (market: Dict): Market { /* ... */ }
    async fetchTicker (symbol: string, params = {}): Promise<Ticker> { /* ... */ }
    parseTicker (ticker: Dict, market: Market = undefined): Ticker { /* ... */ }
    // ...

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        // build URL, sign request — use this.hmac, this.jwt, this.ecdsa, never external libs
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        // throw the right exception based on response
    }
}

Step 3 — Define implicit API methods

URLs in the api block become methods automatically:

  • 'symbols' → this.publicGetSymbols(params)
  • 'ticker/{pair}' → this.publicGetTickerPair({ pair: market['id'] })
  • 'orders' under private.get → this.privateGetOrders(params)

Don't write explicit HTTP wrappers. After listing the URL in api, run npm run emitAPITs to regenerate ts/src/abstract/<id>.ts (the auto-typed declarations).

Step 4 — Docstrings on every public method

Every public method needs a JSDoc block. The transpilers convert it to native docstrings in Python/PHP/C#/Go, and npm run build-docs produces wiki entries from them. Pattern:

/**
 * @method
 * @name <id>#fetchMyTrades
 * @description fetches all completed trades made by the user
 * @see https://docs.<id>.com/api/trades                 // spot
 * @see https://docs.<id>.com/api/derivatives/trades     // swap
 * @param {string} symbol unified market symbol
 * @param {int} [since] earliest timestamp in ms
 * @param {int} [limit] maximum number of trades to return
 * @param {object} [params] extra parameters specific to the exchange API endpoint
 * @param {int} [params.until] latest timestamp in ms
 * @returns {object[]} a list of [trade structures](https://docs.ccxt.com/#/?id=trade-structure)
 */
async fetchMyTrades (symbol: Str = undefined, since: Int = undefined, limit: Int = undefined, params = {}): Promise<Trade[]> {
    // ...
}

Rules: lowercase @description, @param {object} [params] always present, document every params.<key> you read in the body, link @returns to the manual structure. See CLAUDE.md §7 for the full ruleset.

Step 5 — Required parsers

For every fetch method, write a matching parser. The parser is what makes output uniform across all exchanges:

Fetch methodParserValidator (test)
fetchMarketsparseMarketts/src/test/Exchange/base/test.market.ts
fetchCurrenciesparseCurrencytest.currency.ts
fetchTickerparseTickertest.ticker.ts
fetchOrderBookbase parseOrderBooktest.orderBook.ts
fetchTrades, fetchMyTradesparseTradetest.trade.ts
fetchOHLCVparseOHLCVtest.ohlcv.ts
fetchBalanceparseBalancetest.balance.ts
createOrder, fetchOrder, fetchOpenOrdersparseOrdertest.order.ts
fetchPositionsparsePositiontest.position.ts

Parsing rules (also in CLAUDE.md §7):

  • Always safeString first, parse with Precise for math, finalise with parseNumber only at the return.
  • Symbol resolution: this.safeSymbol(marketId, market) — never put exchange-specific ids into unified output.
  • Time: convert seconds → ms using safeTimestamp; everything in unified output is ms.

Step 6 — Static fixtures (TDD)

Capture a request/response fixture as soon as a method works once. Re-run on every change.

🚨 Never hand-write or invent a static fixture — always capture it with the CLI. --static performs a real call and records the actual URL, body and HTTP response. A fabricated fixture asserts what you assumed the exchange does, so the test goes green while the integration is broken — and it then becomes the reference every language is verified against. If you cannot reach the endpoint (no credentials, geo-block, venue down), ship no fixture and say so explicitly; do not guess one.

# capture BOTH the request and the response entry from one live call, and save them
npm run cli.ts -- <id> fetchTicker BTC/USDT --static --name "spot ticker"

--static writes a methods.fetchTicker entry into both ts/src/test/static/request/<id>.json and ts/src/test/static/response/<id>.json. Details:

  • --name "…" auto-saves. Without it the two entries are only printed, so you can review before saving.
  • --request / --response capture just one side; --static is both.
  • watch* methods: --static records ws frames until you press ctrl+c and writes a ts/src/test/static/ws/<id>.json entry. --recordLimit <n> keeps only the first n resolutions.
  • Prediction exchanges land in the static/{request,response}/prediction/ subfolder automatically.

Then run:

npm run request-tests
npm run response-tests

The capture hits the network; the tests replay the recording with no HTTP, in every language, and are your primary regression net.

Step 7 — Verify in all languages

A new exchange means thousands of new lines in Python, PHP, C#, Go and Java. The transpilers must like all of it.

npm run lint
npm run tsBuild
npm run transpile          # → Python, PHP
npm run transpileCS        # → C#
npm run buildCS
npm run transpileGO        # → Go
npm run buildGO
npm run check-python-syntax
npm run check-php-syntax
npm run id-tests
npm run request-tests
npm run response-tests

Then a live smoke test on at least one public method per language:

npm run cli.ts -- <id> fetchTicker BTC/USDT --verbose
npm run cli.py -- <id> fetchTicker BTC/USDT --verbose
npm run cli.php -- <id> fetchTicker BTC/USDT --verbose
npm run cli.cs -- <id> fetchTicker BTC/USDT --verbose
npm run cli.go -- <id> fetchTicker BTC/USDT --verbose

Step 8 — WebSocket support (optional)

If the exchange has WS, create ts/src/pro/<id>.ts. It must extends <id>Rest and add watch* methods. Reference: ts/src/pro/binance.ts. Flip 'pro': true in REST describe().

Step 9 — Update user-facing docs

A new exchange means new public surface area. Update the touchpoints listed in CLAUDE.md §8:

  • wiki/Manual.md doesn't usually list per-exchange specifics, but add a section if your exchange introduces a new pattern (e.g. a new auth scheme, new params).
  • Verify examples/ts/<id>-example.ts has at least one runnable snippet (transpiled to other languages by npm run tsBuildExamples).
  • The end-user skills under .claude/skills/ccxt-{typescript,python,php,csharp,go}/ mention "all 100+ exchanges" — usually no edit needed unless your exchange has unique credential requirements (e.g. wallet/private key) worth calling out.
  • npm run build-docs regenerates wiki entries from your JSDoc — run it once and inspect the output.

Step 10 — PR

Title: feat(<id>): add <Name> integration. Description follows the template in CLAUDE.md §11 — list every test you ran with results, and reference any related issue/PR.

Output checklist

  • ts/src/<id>.ts written (uses Precise, safeString*, safeSymbol)
  • Every public method has a JSDoc block (CLAUDE.md §7)
  • 'has' flags accurately reflect implemented methods
  • 'urls.test' set if exchange has a testnet
  • 'requiredCredentials' matches what sign() actually uses
  • All required parsers (parseMarket, parseTicker, parseTrade, parseOrder, …)
  • handleErrors maps exchange error codes to CCXT exception classes
  • ts/src/abstract/<id>.ts regenerated via npm run emitAPITs
  • Static request + response fixtures for every implemented method
  • npm run lint && npm run tsBuild && npm run transpile && npm run transpileCS && npm run transpileGO all pass
  • npm run id-tests && npm run request-tests && npm run response-tests all pass
  • Live smoke tested in TS + at least one transpiled language (cli.py, cli.cs, cli.go, …) with --verbose
  • npm run build-docs ran, generated wiki entries look correct
  • User-facing docs reviewed (CLAUDE.md §8) — Manual.md / examples / language skills if anything is non-standard
  • PR title follows feat(<id>): ...; description fills CLAUDE.md §11 template

Prediction-market exchange variant

A prediction-market venue (Polymarket-style: events → markets → binary/categorical outcomes) does not extend Exchange — it lives in its own namespace. The differences from the checklist above:

  1. File — ts/src/prediction/<id>.ts, class <id> extends PredictionExchange (import Exchange from ./abstract/prediction/<id>.js). Read .claude/rules/prediction-outcomes.md for the outcome-cache contract and ts/src/base/PredictionExchange.ts for the base helpers you inherit (loadOutcome/loadOutcomes/fetchOutcome/populateOutcomes/indexMarketOutcomes/safeOutcome/outcome, setEvents/getEvent/eventsList/applyEventFetchParams, parsePredictionTrades/parsePredictionOrders/parsePredictionPositions, safePrediction*).
  2. describe() — has.prediction: true; address methods by an outcome handle, not a symbol. Implement fetchEvents(params) (scope-required via requireEventQuery) and, if the venue has a single-event endpoint, fetchEvent(id). Return Prediction* types from ts/src/base/types.ts (never base Ticker/Order/…). loadAllOutcomes defaults to false (a cache miss resolves one outcome via the base search-backed fetchOutcome); override fetchOutcome with a by-id fetch when the venue has one (see kalshi), and set loadAllOutcomes: true only when the whole universe is a single cheap request (see hyperliquid). No-arg fetchTickers() must throw ArgumentsRequired unless the venue has a true all-tickers endpoint.
  3. Never call the base parseTrades/parseOrders/parsePositions — they filter by symbol and drop prediction rows. Use parsePredictionTrades/Orders/Positions. Never call buildOHLCVC (transpiles to a mangled name) — bucket candles inline.
  4. Registration — npm run export-exchanges adds the id to exchanges.json prediction[] and wires ts/ccxt.ts / the per-language namespaces / README table. npm run emitAPI emits ts/src/abstract/prediction/<id>.ts. Add a skip-tests.json entry with preferredEventQuery (the harness's fetchEvents scope) and preferredPredictionOutcome (a tradeable handle — validated against the live listing).
  5. Transpile — the scoped single-exchange transpilers auto-route a bare prediction-only id (tsx build/transpile.ts <id>, csharpTranspiler.ts <id>, goTranspiler.ts <id>). After editing PredictionExchange.ts you must also regen the base per language: tsx build/{transpile,csharpTranspiler,goTranspiler,javaTranspiler}.ts --baseClass. Any base→override call (like fetchOutcome) must be registered in VIRTUAL_BASE_METHODS in build/goTranspiler.ts or Go won't dispatch it.
  6. Test — node run-tests <id> --js --prediction --private (add --sandbox for demo hosts like kalshi). Static fixtures live in the normal flat ts/src/test/static/{request,response}/<id>.json; the python/php sync harness skips prediction (they carry "asyncOnly": true).
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

.claude/skills/new-exchange

默认分支

master

最新提交

1259174

Tree SHA

663c755