@tickerall/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miguel Santos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # @tickerall/sdk
2
+
3
+ Official TypeScript client for the [TickerAll](https://tickerall.com) REST + WebSocket API.
4
+
5
+ Place trades, stream live market data, and manage broker sessions programmatically — without an MT4/MT5 terminal in the path.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @tickerall/sdk
11
+ ```
12
+
13
+ Requires Node.js 18 or later (native `fetch`). Bun and Deno are also supported.
14
+
15
+ ## Quickstart
16
+
17
+ ```ts
18
+ import { Tickerall } from '@tickerall/sdk'
19
+
20
+ const ticker = new Tickerall({ apiKey: process.env.TICKERALL_API_KEY! })
21
+
22
+ const { accountId } = await ticker.sessions.start({
23
+ broker: 'mt4',
24
+ server: 'Exness-Trial8',
25
+ account: 69542056,
26
+ password: process.env.MT4_PASSWORD!,
27
+ })
28
+
29
+ const order = await ticker.orders.place(accountId, {
30
+ type: 'market',
31
+ symbol: 'EURUSDm',
32
+ side: 'BUY',
33
+ volume: 0.1,
34
+ stopLoss: 1.0800,
35
+ takeProfit: 1.1000,
36
+ })
37
+
38
+ await ticker.sessions.end(accountId)
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ ```ts
44
+ new Tickerall({
45
+ apiKey: 'cf_live_...', // required
46
+ baseUrl: 'https://api.tickerall.com', // optional; override for staging
47
+ streamUrl: 'wss://api.tickerall.com/v1/stream',
48
+ timeout: 30_000, // ms; per-request default
49
+ fetch: globalThis.fetch, // injection point for tests
50
+ userAgent: 'my-algo/1.0', // tacked onto outgoing UA
51
+ })
52
+ ```
53
+
54
+ Multiple `Tickerall` instances are independent — useful for multi-tenant code.
55
+
56
+ ## Sessions
57
+
58
+ ```ts
59
+ const session = await ticker.sessions.start({
60
+ broker: 'mt4',
61
+ server: 'Exness-Trial8',
62
+ account: 69542056,
63
+ password: '...',
64
+ })
65
+ // { accountId, isDemo, status: 'connected', expiresAt }
66
+
67
+ await ticker.sessions.end(session.accountId)
68
+ ```
69
+
70
+ Credentials are sent on each `sessions.start`. TickerAll never persists broker passwords; a session lives only as long as the underlying broker connection.
71
+
72
+ ## Accounts
73
+
74
+ ```ts
75
+ const accounts = await ticker.accounts.list()
76
+ const account = await ticker.accounts.get(accountId)
77
+ const symbols = await ticker.accounts.symbols(accountId)
78
+ ```
79
+
80
+ If `account.status === 'offline'`, call `sessions.start` again with credentials to reconnect.
81
+
82
+ ## Candles (historical OHLC)
83
+
84
+ ```ts
85
+ const bars = await ticker.candles.get({ symbol: 'BTCUSDm', hours: 8760, timeframe: 'D1' })
86
+ // [{ timestamp, open, high, low, close, bid }, ...]
87
+ // timestamp is the bar OPEN time (Unix seconds, UTC); bid mirrors close.
88
+ ```
89
+
90
+ Public endpoint — available on every plan (the API key is sent but isn't required). Timeframes: `M1 M5 M15 M30 H1 H4 D1 W1 MN1` (defaults to `M5`). Coarser timeframes reach further back; one request returns as much history as fits in a few seconds, so pass a large `hours` and take what comes back.
91
+
92
+ ## Orders + positions
93
+
94
+ ```ts
95
+ const order = await ticker.orders.place(accountId, {
96
+ type: 'market',
97
+ symbol: 'EURUSDm',
98
+ side: 'BUY',
99
+ volume: 0.1,
100
+ })
101
+ // { ticket, symbol, side, type, volume, status, timestamp, ... }
102
+
103
+ const limit = await ticker.orders.place(accountId, {
104
+ type: 'limit',
105
+ symbol: 'EURUSDm',
106
+ side: 'BUY',
107
+ volume: 0.1,
108
+ price: 1.0800,
109
+ })
110
+
111
+ await ticker.positions.modify(accountId, order.ticket, { stopLoss: 1.0850 })
112
+ await ticker.positions.close(accountId, order.ticket)
113
+ await ticker.positions.close(accountId, order.ticket, { volume: 0.05 }) // partial close
114
+ ```
115
+
116
+ ## Idempotency
117
+
118
+ State-changing endpoints (`sessions.start`, `orders.place`, `positions.close`, `positions.modify`) auto-generate an `Idempotency-Key` (UUID v4) per call. Pass your own to deduplicate retries on your side:
119
+
120
+ ```ts
121
+ await ticker.orders.place(
122
+ accountId,
123
+ { type: 'market', symbol: 'EURUSDm', side: 'BUY', volume: 0.1 },
124
+ { idempotencyKey: 'my-strategy-tick-12345' },
125
+ )
126
+ ```
127
+
128
+ ## Resilience
129
+
130
+ TickerAll keeps a fast, always-on connection to your broker, and the SDK is built so a momentary backend blip — a deploy, a restart, a network hiccup — **doesn't break an idle app**. The stream reconnects silently and re-sends your subscriptions; nothing is thrown unless you actively try to do something during the window.
131
+
132
+ **Trades fail fast by default.** If you place an order while TickerAll is momentarily unreachable, you get a `TickerallServiceUnavailableError` (`err.transient === true`) right away — so you can re-decide with fresh prices rather than fire a stale order late:
133
+
134
+ ```ts
135
+ try {
136
+ await ticker.orders.place(accountId, params)
137
+ } catch (err) {
138
+ if (err instanceof TickerallServiceUnavailableError) {
139
+ // momentarily unreachable — retry, or skip this tick
140
+ }
141
+ }
142
+ ```
143
+
144
+ **Queue-and-replay (opt-in).** For calls that aren't price-sensitive (a pending order, an SL/TP edit), pass `queueIfReconnecting` to have the SDK hold the call and replay it — with its stable idempotency key, so it can't double-execute — until connectivity returns or `queueMaxMs` (default 60s) elapses:
145
+
146
+ ```ts
147
+ await ticker.positions.modify(
148
+ accountId, ticket, { stopLoss: 1.0850 },
149
+ { queueIfReconnecting: true, queueMaxMs: 30_000 },
150
+ )
151
+ ```
152
+
153
+ Queued calls replay in submission order. Avoid this for market orders — a delayed fill at a moved price is usually worse than a fast failure.
154
+
155
+ ### Keep a session alive (auto re-arm)
156
+
157
+ For an account you want to stay connected with no manual reconnect, start it with `keepAlive` instead of `start`. The SDK caches the broker credentials **in your process's memory** (never persisted) and transparently re-supplies them — re-arming the session — if the account ever goes cold (e.g. TickerAll restarted and dropped its server-side credentials):
158
+
159
+ ```ts
160
+ const { accountId } = await ticker.sessions.keepAlive({
161
+ broker: 'mt5', server: 'Exness-...', account: 123, password: process.env.MT5_PASSWORD!,
162
+ })
163
+
164
+ // A later call that hits a cold account auto-re-arms and retries — once:
165
+ await ticker.orders.place(accountId, { type: 'market', symbol: 'ETHUSDm', side: 'BUY', volume: 0.1 })
166
+
167
+ ticker.sessions.stopKeepAlive(accountId) // forget the cached credentials
168
+ ```
169
+
170
+ - The live **stream** re-arms kept sessions automatically when it reconnects, so subscriptions resume against a hot account.
171
+ - Observe re-arms with the `onRearm` hook: `new Tickerall({ apiKey, onRearm: id => log('re-armed', id) })`.
172
+ - Proactively recover after a known outage: `await ticker.sessions.rearmPending()` re-arms every kept account that TickerAll reports cold (or inspect `await ticker.sessions.pendingRearm()` yourself).
173
+ - Credentials live in RAM only and are dropped on `stopKeepAlive` or `sessions.end`. **TickerAll never persists broker passwords** — which is also why, after a TickerAll restart, a kept session is recovered by *your* SDK re-supplying them (automatically), not by us having stored them.
174
+
175
+ ## Live stream
176
+
177
+ ```ts
178
+ const stream = await ticker.stream.connect()
179
+
180
+ await stream.subscribeTicks(accountId, ['EURUSDm', 'XAUUSDm'])
181
+ await stream.subscribePositions(accountId)
182
+ await stream.subscribeAccount(accountId)
183
+
184
+ stream.on('tick', e => console.log(e.symbol, e.bid, e.ask))
185
+ stream.on('position', e => console.log(e.event, e.position))
186
+ stream.on('account', e => console.log(e.snapshot.balance))
187
+ stream.on('error', e => console.error(e.message))
188
+ stream.on('reconnect', e => console.log('reconnecting, attempt', e.attempt))
189
+ stream.on('close', e => console.log('closed:', e.code, e.reason))
190
+
191
+ await stream.close()
192
+ ```
193
+
194
+ Connection state is queryable any time — never pushed at you, so an idle app stays quiet:
195
+
196
+ ```ts
197
+ stream.getState() // 'connecting' | 'open' | 'reconnecting' | 'closed'
198
+ stream.isConnected() // true only when live
199
+ await stream.waitUntilConnected(10_000) // resolve when open, reject on timeout
200
+ ```
201
+
202
+ The stream:
203
+
204
+ - Reconnects on disconnect with exponential backoff (1s, 2s, 4s, 8s, 16s, max 30s, with jitter) — silently, so an idle app never breaks on a backend blip.
205
+ - Re-sends all known subscriptions on reconnect.
206
+ - Sends a heartbeat every 25 seconds; force-reconnects on missed pong.
207
+ - A missing `error` listener never crashes your process; subscribe to `'reconnect'`/`'close'` only if you want to observe it.
208
+ - Does not buffer missed ticks (mirrors the server contract — resubscribe and discard stale state if you reconnect).
209
+
210
+ ## Errors
211
+
212
+ All API errors extend `TickerallApiError`. Check `err.status` and `err.code`:
213
+
214
+ ```ts
215
+ import {
216
+ TickerallApiError,
217
+ TickerallAuthError,
218
+ TickerallForbiddenError,
219
+ TickerallValidationError,
220
+ TickerallNotFoundError,
221
+ TickerallBrokerError,
222
+ TickerallServiceUnavailableError,
223
+ } from '@tickerall/sdk'
224
+
225
+ try {
226
+ await ticker.orders.place(accountId, params)
227
+ } catch (err) {
228
+ if (err instanceof TickerallServiceUnavailableError) {
229
+ // TickerAll was momentarily unreachable (network blip, deploy, restart).
230
+ // The request never reached a verdict — safe to retry. See "Resilience".
231
+ } else if (err instanceof TickerallBrokerError) {
232
+ // broker rejected the order, broker is offline, etc.
233
+ } else if (err instanceof TickerallForbiddenError && err.code === 'FREE_TIER_LIVE_REJECTED') {
234
+ // user is on the free tier — upgrade to use live accounts
235
+ } else if (err instanceof TickerallValidationError) {
236
+ console.error('bad input:', err.details)
237
+ } else if (err instanceof TickerallApiError) {
238
+ console.error(err.status, err.code, err.message, err.requestId)
239
+ }
240
+ }
241
+ ```
242
+
243
+ Every error carries a boolean `err.transient` — `true` only for `TickerallServiceUnavailableError` (a connectivity blip you can safely retry), `false` for everything else (auth, validation, broker rejection — these won't change on retry).
244
+
245
+ | Error class | Status / code | Notes |
246
+ | ---------------------------------- | ----------------------------------- | ----------------------------------------------------------- |
247
+ | `TickerallAuthError` | 401 | invalid or missing API key |
248
+ | `TickerallForbiddenError` | 403 | includes `FREE_TIER_LIVE_REJECTED` |
249
+ | `TickerallValidationError` | 400 / 422 | bad request body or params |
250
+ | `TickerallNotFoundError` | 404 | unknown account, ticket, etc. |
251
+ | `TickerallBrokerError` | broker-coded (e.g. `BROKER_*`) | broker rejection or broker connection issues |
252
+ | `TickerallServiceUnavailableError` | network / timeout / bare 502·503 | **transient** — TickerAll momentarily unreachable; retry |
253
+ | `TickerallApiError` | any | base class; check `.code` and `.transient` for specifics |
254
+
255
+ ## Timeouts and cancellation
256
+
257
+ ```ts
258
+ await ticker.accounts.list({ timeout: 5_000 })
259
+
260
+ const controller = new AbortController()
261
+ setTimeout(() => controller.abort(), 1_000)
262
+ await ticker.accounts.list({ signal: controller.signal })
263
+ ```
264
+
265
+ ## TypeScript
266
+
267
+ The SDK ships hand-written `.d.ts` files. All public types are exported from the root:
268
+
269
+ ```ts
270
+ import type { PlaceOrderParams, Position, TickEvent } from '@tickerall/sdk'
271
+ ```
272
+
273
+ ## License
274
+
275
+ MIT