@taphubhq/sdk-core 0.25.5 → 0.25.6
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/README.md +351 -9
- package/dist/index.cjs +119 -2
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +114 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @taphubhq/sdk-core
|
|
2
2
|
|
|
3
|
-
Framework-agnostic TypeScript SDK for the
|
|
3
|
+
Framework-agnostic TypeScript SDK for the TapHub platform. Wraps the grid-api and
|
|
4
|
+
user-service GraphQL endpoints plus the MQTT realtime streams behind one client.
|
|
5
|
+
|
|
6
|
+
For React bindings (hooks, providers, i18n), use
|
|
7
|
+
[`@taphubhq/sdk-react`](https://github.com/TaphubHQ/taphub-sdk/blob/main/packages/sdk-react/README.md), which builds on this package.
|
|
4
8
|
|
|
5
9
|
## Install
|
|
6
10
|
|
|
@@ -16,9 +20,11 @@ import { TaphubClient } from '@taphubhq/sdk-core';
|
|
|
16
20
|
const client = new TaphubClient({
|
|
17
21
|
agencyId: 'acme-corp',
|
|
18
22
|
// Shared API base. The SDK derives:
|
|
19
|
-
// - grid-api
|
|
23
|
+
// - grid-api → ${endpoint}/grid-api/grid-gql
|
|
20
24
|
// - user-service → ${endpoint}/taphub-user-service/th-user-gql
|
|
21
25
|
endpoint: 'https://builder.taptrading.net/api',
|
|
26
|
+
// Optional — omit to run without realtime (client.realtime stays undefined).
|
|
27
|
+
mqttEndpoint: 'wss://mqtt.taptrading.net/mqtt',
|
|
22
28
|
});
|
|
23
29
|
|
|
24
30
|
// Session-token login via user-service GraphQL (preferred).
|
|
@@ -27,16 +33,276 @@ await client.auth.loginWithSession(upstreamSessionToken);
|
|
|
27
33
|
const me = await client.user.me();
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
###
|
|
36
|
+
### Config
|
|
37
|
+
|
|
38
|
+
| Field | Required | Notes |
|
|
39
|
+
|----------------|----------|-------|
|
|
40
|
+
| `agencyId` | yes | Sent as `x-builder-code` on every request. Also namespaces token storage and scopes agency MQTT topics. |
|
|
41
|
+
| `endpoint` | yes | Absolute URL of the shared API base. Must parse as a URL — the constructor throws `TaphubValidationError` (`code: 'InvalidConfig'`) otherwise. |
|
|
42
|
+
| `mqttEndpoint` | no | When absent, `client.realtime` is `undefined`; every other module still works. |
|
|
43
|
+
| `mqttAuth` | no | `{ username, password }`, forwarded verbatim to the broker. For the TapHub/EMQX JWT-in-username setup, pass the session token as `username` and `''` as `password`. Read once when the connection opens — no refresh on reconnect. Ignored without `mqttEndpoint`. |
|
|
44
|
+
| `storage` | no | `TaphubStorageAdapter` for token persistence. Defaults to `localStorage` in the browser, in-memory elsewhere. |
|
|
45
|
+
| `fetch` | no | Custom `fetch` implementation (SSR, instrumentation, test doubles). |
|
|
46
|
+
|
|
47
|
+
### Naming: `pairId` vs `agencyPairId`
|
|
48
|
+
|
|
49
|
+
- **`pairId`** — the system-wide pair id, e.g. `grid-ETH-USD`. **Slash-free** by
|
|
50
|
+
design so it is safe inside MQTT topics, URLs, and Redis keys. This is what
|
|
51
|
+
every SDK method takes.
|
|
52
|
+
- **`agencyPairId`** — the agency-scoped composite `{agencyId}:{pairId}`, e.g.
|
|
53
|
+
`acme-corp:grid-ETH-USD`. The server composes it from your `x-builder-code` /
|
|
54
|
+
JWT; you only meet it as an MQTT `gameId` and on `PairInfo.agencyPairId`.
|
|
55
|
+
- **`ETH/USD`** is a *display* name (`Pair.pair`, `AgencyPairStats.pair`), never
|
|
56
|
+
an id.
|
|
57
|
+
|
|
58
|
+
The pre-`bid-260602-v2` names `game` / `gameId` / `gamePairId` are retired from
|
|
59
|
+
the public surface.
|
|
60
|
+
|
|
61
|
+
## Client surface
|
|
62
|
+
|
|
63
|
+
| Accessor | Module | Auth |
|
|
64
|
+
|---------------------|-------------------------|------|
|
|
65
|
+
| `client.auth` | `AuthModule` | — |
|
|
66
|
+
| `client.user` | `UserModule` | JWT |
|
|
67
|
+
| `client.pair` | `PairModule` | JWT (anon-tolerant) |
|
|
68
|
+
| `client.bid` | `BidModule` | JWT |
|
|
69
|
+
| `client.leaderboard`| `LeaderboardModule` | mixed (see below) |
|
|
70
|
+
| `client.agencyPairs`| `AgencyPairModule` | anon (`x-builder-code`) |
|
|
71
|
+
| `client.locale` | `LocaleModule` | anon |
|
|
72
|
+
| `client.realtime` | `RealtimeModule \| undefined` | broker creds |
|
|
73
|
+
| `client.network` | `NetworkQualityMonitor` | — |
|
|
74
|
+
|
|
75
|
+
Token helpers live on the client itself: `getToken()`, `setToken(token, { isDemo })`,
|
|
76
|
+
`isDemo()`.
|
|
77
|
+
|
|
78
|
+
> **Anon vs JWT reads.** `agencyPairs.list()`, `leaderboard.list()` and
|
|
79
|
+
> `leaderboard.rankBoard()` deliberately send **`x-builder-code` only, never the
|
|
80
|
+
> JWT**. A signed-in user can hold a JWT for a different agency than the one in
|
|
81
|
+
> context (a builder's demo user is always issued a `taptrading` JWT); attaching
|
|
82
|
+
> it would make the backend take its "JWT wins" branch and return the wrong
|
|
83
|
+
> agency's data.
|
|
84
|
+
|
|
85
|
+
## Auth
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
await client.auth.loginWithSession(sessionToken); // → LoginResult
|
|
89
|
+
await client.auth.logout();
|
|
90
|
+
```
|
|
31
91
|
|
|
32
|
-
| Method
|
|
33
|
-
|
|
92
|
+
| Method | Transport | Status |
|
|
93
|
+
|----------------------------------|----------------------|---------------|
|
|
34
94
|
| `loginWithSession(sessionToken)` | user-service GraphQL | **Preferred** |
|
|
35
|
-
| `createDemoUser(username?)`
|
|
36
|
-
| `loginWithGoogle(idToken)`
|
|
37
|
-
| `demoLogin(username?)`
|
|
95
|
+
| `createDemoUser(username?, opts?)` | grid-api GraphQL | Active |
|
|
96
|
+
| `loginWithGoogle(idToken)` | grid-api REST | `@deprecated` |
|
|
97
|
+
| `demoLogin(username?)` | grid-api REST | `@deprecated` |
|
|
98
|
+
|
|
99
|
+
REST-backed methods continue to work until the gateway retires the routes in a
|
|
100
|
+
follow-up change.
|
|
101
|
+
|
|
102
|
+
## User
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const me = await client.user.me(); // Me
|
|
106
|
+
const pnl = await client.user.pnl({ period: 'weekly' }); // UserPnL — 'weekly' | '30d'
|
|
107
|
+
const wallets = await client.user.wallets(); // UserWallet[]
|
|
108
|
+
const usdc = await client.user.walletByCurrency('USDC.e'); // UserWallet
|
|
109
|
+
const enabled = await client.user.refreshCurrencies(); // Currency[]
|
|
110
|
+
client.user.clearCurrencies(); // drop the cached list
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`UserPnL` carries `gain` (= `totalPayout − totalWagered`), the wagered/payout
|
|
114
|
+
totals, bid/win counts, and both competition ranks (`pnlRank`, `volRank`; `0`
|
|
115
|
+
means unranked).
|
|
116
|
+
|
|
117
|
+
> `client.user.wallets()` returns `UserWallet` (`{ id, amount, currency, isEnable }`),
|
|
118
|
+
> which is **not** the `Wallet` returned by the auth module (`{ balance, isEnabled }`).
|
|
119
|
+
> The distinct export alias is deliberate — see the note in `src/index.ts`.
|
|
120
|
+
|
|
121
|
+
## Pairs
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const pairs = await client.pair.availableGamePairs({ gameplayId: 'taptrading' }); // PairInfo[]
|
|
125
|
+
const pair = await client.pair.get('grid-ETH-USD'); // Pair
|
|
126
|
+
const bars = await client.pair.chartHistory('grid-ETH-USD'); // Candle[]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `get(pairId)` throws `TaphubValidationError` with `code: 'GameNotFound'` when
|
|
130
|
+
the pair has no configured game for the agency in context. It also feeds the
|
|
131
|
+
clock-offset estimate (see [Clock sync](#clock-sync)).
|
|
132
|
+
- `chartHistory(pairId, limit?)` defaults to `DEFAULT_CHART_HISTORY_LIMIT` (600);
|
|
133
|
+
the server clamps any request to its cache size (1500).
|
|
134
|
+
- **`Candle.time` from GraphQL is Unix epoch SECONDS.** The MQTT
|
|
135
|
+
`MqttCandleEvent.time` is **milliseconds**. Do not multiply the MQTT value.
|
|
136
|
+
- `PairInfo.agencyPairId` is populated only for authenticated callers, and is the
|
|
137
|
+
value to feed into `realtime.subscribe()`.
|
|
138
|
+
- `Pair.config` carries `gridConfig` (`cellSizeTime`, `cellSizeValue`,
|
|
139
|
+
`candleSize`, `baseline`, `baselineTime`), `constraints`, bid amount bounds, and
|
|
140
|
+
the effective cancel policy (`bidCancelMinSeconds`, `bidCancelRefundRate`).
|
|
141
|
+
Treat `undefined` / `0` on the cancel fields as "use the platform default".
|
|
142
|
+
|
|
143
|
+
## Bids
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const bid = await client.bid.placeBid({
|
|
147
|
+
pairId: 'grid-ETH-USD',
|
|
148
|
+
walletId, // required — grid-api's wallet_id is ID!
|
|
149
|
+
time1, time2, // window, unix seconds
|
|
150
|
+
price1, price2, // decimal strings
|
|
151
|
+
coefficient: '2.35',
|
|
152
|
+
amount: '10',
|
|
153
|
+
slippage: 5, // tolerance percent
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = await client.bid.cancelBid(bid.id); // { bid, refundAmount, newBalance }
|
|
157
|
+
const mine = await client.bid.listBids({ statuses: ['pending'], limit: 20, pairId });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`placeBid` auto-stamps `ct` (raw client send time) and `clockOffset` at dispatch
|
|
161
|
+
so the server can record skew-corrected latency into `bid.meta`. Both are
|
|
162
|
+
untrusted tracing data — do not supply them yourself.
|
|
163
|
+
|
|
164
|
+
`listBids` takes `statuses: BidStatus[]` (include-semantics). The singular
|
|
165
|
+
`status` is `@deprecated` and folded into a one-element list; `statuses` wins when
|
|
166
|
+
both are present.
|
|
167
|
+
|
|
168
|
+
Status classifiers avoid string-literal typos:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { isPending, isTerminal, isWin, isLoss, isCancelled } from '@taphubhq/sdk-core';
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`Bid.refundRate` / `Bid.refundAmount` are present only on cancelled bids. Prefer
|
|
175
|
+
`refundAmount` for display — it is the exact credited value, so it needs no
|
|
176
|
+
recompute from a config rate. `Bid.meta.pairName` holds the display pair frozen
|
|
177
|
+
at placement; read it instead of mapping `pairId` yourself.
|
|
178
|
+
|
|
179
|
+
## Leaderboard & rank
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const board = await client.leaderboard.rankBoard({
|
|
183
|
+
period: 'last_7d', // 'today' | 'yesterday' | 'last_7d' | 'last_30d' | 'all_time' | 'custom'
|
|
184
|
+
sort: 'pnl', // 'pnl' | 'vol'
|
|
185
|
+
sortDir: 'desc',
|
|
186
|
+
limit: 50, offset: 0,
|
|
187
|
+
}); // RankBoard — public, identical for every caller
|
|
188
|
+
|
|
189
|
+
const me = await client.leaderboard.myRank({ period: 'last_7d' }); // MyRank | null
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `period: 'custom'` requires an **hour-aligned** ISO-8601 UTC `range`.
|
|
193
|
+
- The board carries no per-user data by design (`bid-260606-leaderboard-split-my-rank`)
|
|
194
|
+
so it stays cacheable — `myRank()` is the per-user companion and does attach
|
|
195
|
+
the JWT. It returns `null` when nobody is signed in, and `MyRank.entry === null`
|
|
196
|
+
when the caller has no activity in the window.
|
|
197
|
+
- `leaderboard.list({ period, sortBy })` is the v1 query and is `@deprecated` on
|
|
198
|
+
both the SDK and the backend.
|
|
199
|
+
|
|
200
|
+
## Agency pairs (market list)
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const rows = await client.agencyPairs.list({ filter: { status: 'active' } }); // AgencyPairStats[]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Returns the agency-in-context's pairs with `currentPrice`, `currentVol24h`
|
|
207
|
+
(sliding 24h bid-amount sum), `maxCoef`, `thumb`, and `ordering`. There is no
|
|
208
|
+
argument to target another agency by design. Seed your UI from this call, then
|
|
209
|
+
keep it live with `realtime.subscribeAgencyPairStats()` — the backend suppresses
|
|
210
|
+
unchanged ticks, so a subscriber may receive nothing until price or volume moves.
|
|
211
|
+
|
|
212
|
+
## Locale & error copy
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const res = await client.locale.get('vi', lastSeenVersion);
|
|
216
|
+
// → { lang, version, notModified, translations }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Pass the last-seen `version` as `knownVersion` to get `{ notModified: true,
|
|
220
|
+
translations: null }` when the body is unchanged, and keep your prior state.
|
|
221
|
+
|
|
222
|
+
Error copy is a separate catalog keyed by the backend `extensions.code`:
|
|
38
223
|
|
|
39
|
-
|
|
224
|
+
```ts
|
|
225
|
+
await client.locale.loadErrorMessages(navigator.language); // never throws
|
|
226
|
+
|
|
227
|
+
// …later, inside a catch:
|
|
228
|
+
showToast(client.locale.errorMessage(e.code, 'Could not place your bid.'));
|
|
229
|
+
|
|
230
|
+
// with placeholders filled from the error's meta:
|
|
231
|
+
client.locale.errorMessage('Bid_CoefficientMismatch', 'Odds changed — try again.', {
|
|
232
|
+
serverCoef: e.serverCoef,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- `loadErrorMessages(lang)` is background hydration: it **never throws and never
|
|
237
|
+
rejects**. Any failure leaves the last-known-good catalog in place — failing to
|
|
238
|
+
translate an error must not itself become an error. The tag is normalised
|
|
239
|
+
(region dropped, Chinese scripts kept apart), and concurrent calls for the same
|
|
240
|
+
language share one request.
|
|
241
|
+
- `errorMessage(code, fallback, meta?)` is **synchronous by design** — error copy
|
|
242
|
+
is read inside a `catch`, where React hooks are illegal. It returns `fallback`
|
|
243
|
+
whenever the catalog cannot produce a usable sentence, never an empty string and
|
|
244
|
+
never the raw code. `meta` fills `{name}` placeholders; unfilled placeholders are
|
|
245
|
+
stripped so raw braces never reach the user.
|
|
246
|
+
- `getErrorMessages(lang, knownVersion?)` is the throwing sibling, for callers who
|
|
247
|
+
want to see why a fetch failed.
|
|
248
|
+
- `refresh()` is an admin operation — the backend re-pulls the source sheet for
|
|
249
|
+
every supported language and requires an `X-API-Key` header.
|
|
250
|
+
|
|
251
|
+
## Realtime (MQTT)
|
|
252
|
+
|
|
253
|
+
`client.realtime` is `undefined` unless `mqttEndpoint` was configured. Channels
|
|
254
|
+
are refcounted: N `subscribe()` calls for the same key return the same channel and
|
|
255
|
+
need N matching `unsubscribe()` calls.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const channel = client.realtime?.subscribe(agencyPairId, userId); // GameChannel
|
|
259
|
+
channel?.on('candle', (e) => drawCandle(e)); // MqttCandleEvent
|
|
260
|
+
channel?.on('bidAccepted', (e) => addLiveTrade(e)); // MqttBidAcceptedEvent
|
|
261
|
+
channel?.on('bidWon', (e) => celebrate(e));
|
|
262
|
+
channel?.on('bidLost', (e) => dim(e));
|
|
263
|
+
channel?.on('bidCancelled', (e) => remove(e.bidId));
|
|
264
|
+
channel?.on('balanceUpdate', (e) => setBalance(e.balance));
|
|
265
|
+
channel?.on('configUpdate', (e) => setBidBounds(e));
|
|
266
|
+
channel?.on('idealConfigUpdate', (e) => suggestCellSize(e));
|
|
267
|
+
channel?.on('error', (err) => report(err));
|
|
268
|
+
client.realtime?.unsubscribe(agencyPairId, userId);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Subscription | Key | Emits |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `subscribe(gameId, userId?)` | `agencyPairId` (+ user) | `GameChannel` — candle, bid lifecycle, balance, config |
|
|
274
|
+
| `subscribeWallet(userId)` | `userId` | `WalletChannel` — `walletBalanceUpdate` on **every** balance change, no game context |
|
|
275
|
+
| `subscribeUserBids(userId)` | `userId` | `UserBidsChannel` — `bidWon` / `bidLost` across **all** pairs, survives pair switches |
|
|
276
|
+
| `subscribeCandle(pairId, handler)` | `pairId` | raw `MqttCandleEvent`, market-wide public data |
|
|
277
|
+
| `subscribeAgencyPairStats(pairId, handler)` | `pairId` | `MqttAgencyPairStatsEvent` for the client's own agency |
|
|
278
|
+
|
|
279
|
+
Each has a matching `unsubscribe*`; `disconnect()` tears everything down.
|
|
280
|
+
|
|
281
|
+
Gotchas worth reading twice:
|
|
282
|
+
|
|
283
|
+
- **`MqttCandleEvent.time` is MILLISECONDS**, unlike the GraphQL `Candle.time`
|
|
284
|
+
(seconds). Compare against `CANDLE_EVENT.NEW` / `CANDLE_EVENT.UPDATE` rather
|
|
285
|
+
than bare string literals.
|
|
286
|
+
- `volatility` is the **fast** σ that prices coefficients; `slowVolatility` is the
|
|
287
|
+
slow σ used for grid geometry only.
|
|
288
|
+
- `MqttAcceptedBid` mirrors the wire shape, **not** the GraphQL `Bid`:
|
|
289
|
+
`coordinates` is nested, `coefficient` / `price1` / `price2` are numbers, and
|
|
290
|
+
`createdAt` is epoch milliseconds. `gameUuid` is `@deprecated` — read `pairId`.
|
|
291
|
+
- Won/lost payloads carry no pair, so the SDK recovers `pairId` from the result
|
|
292
|
+
topic. `pairIdFromBidResultTopic(topic)` is exported if you parse topics
|
|
293
|
+
yourself; it returns `undefined` on an unrecognised shape rather than throwing.
|
|
294
|
+
- `subscribeAgencyPairStats` throws `TaphubError` (`code: 'AgencyIdRequired'`) if
|
|
295
|
+
the client was built without an `agencyId`.
|
|
296
|
+
- `MqttWalletBalanceEvent.reason` is an open enum — unknown values are passed
|
|
297
|
+
through rather than dropped, so handle the default case.
|
|
298
|
+
|
|
299
|
+
## Clock sync
|
|
300
|
+
|
|
301
|
+
`client.pair.get()` brackets its request to measure RTT and records a one-shot
|
|
302
|
+
server-clock offset. `placeBid` then stamps that offset alongside the raw client
|
|
303
|
+
send time. This is best-effort: the estimate is set-once and finite-guarded, so it
|
|
304
|
+
never throws and never blocks the pair load. Nothing to configure — just be aware
|
|
305
|
+
that a client which never calls `pair.get()` reports an offset of `0`.
|
|
40
306
|
|
|
41
307
|
## Error Handling
|
|
42
308
|
|
|
@@ -81,6 +347,9 @@ The GraphQL transport classifies errors by the first entry's `extensions.code` a
|
|
|
81
347
|
|
|
82
348
|
`TaphubSlippageError extends TaphubValidationError`, so existing `instanceof TaphubValidationError` catch blocks keep working. Place the `TaphubSlippageError` check first when both branches exist.
|
|
83
349
|
|
|
350
|
+
To turn a thrown `code` into a user-facing sentence, use
|
|
351
|
+
[`client.locale.errorMessage()`](#locale--error-copy) rather than hardcoding copy.
|
|
352
|
+
|
|
84
353
|
### Slippage error fallback
|
|
85
354
|
|
|
86
355
|
`TaphubSlippageError` is only thrown when `extensions.meta.clientCoef`, `extensions.meta.serverCoef`, and `extensions.meta.slippage` are all finite numbers. The `slippage` field is the client-submitted tolerance percent (e.g. `5` for 5%). If `meta` is missing or malformed, the SDK falls back to a generic `TaphubValidationError` with `code === 'Bid_CoefficientMismatch'`. Consumers SHOULD treat the generic fallback as the same user-facing rejection but without typed coefficient access.
|
|
@@ -114,6 +383,79 @@ The monitor is always present, even when `mqttEndpoint` is not configured. In SS
|
|
|
114
383
|
- Connection samples from `navigator.connection` are used only as a cold-start fallback; once 3 real samples accumulate they no longer contribute to the EMA.
|
|
115
384
|
- MQTT QoS1 `puback` timing is not yet plumbed; v1 derives MQTT signal from `connect` / `reconnect` / `disconnect` events only.
|
|
116
385
|
|
|
386
|
+
## Events
|
|
387
|
+
|
|
388
|
+
A small client-wide bus for cross-cutting signals:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import type { TaphubEventMap } from '@taphubhq/sdk-core';
|
|
392
|
+
// 'auth-expired' → void
|
|
393
|
+
// 'network:change' → { previous: NetworkQuality; current: NetworkQuality }
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Storage
|
|
397
|
+
|
|
398
|
+
Token persistence goes through a 3-method adapter, namespaced per agency
|
|
399
|
+
(`taphub:{agencyId}:token`):
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { TaphubStorage, autoDetectStorage } from '@taphubhq/sdk-core';
|
|
403
|
+
|
|
404
|
+
new TaphubClient({ ..., storage: TaphubStorage.memory() }); // SSR / tests
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`autoDetectStorage()` is the default: `localStorage` in the browser, in-memory
|
|
408
|
+
elsewhere. Implement `TaphubStorageAdapter` (`get` / `set` / `remove`) for
|
|
409
|
+
anything else.
|
|
410
|
+
|
|
411
|
+
## Coefficient math
|
|
412
|
+
|
|
413
|
+
`placeBid` takes a `coefficient`, so a headless client has to compute one before it
|
|
414
|
+
can bid. `calculateCoefficientWrapper` is the model the server validates against —
|
|
415
|
+
the same first-passage computation the grid chart renders with, and the same one
|
|
416
|
+
grid-api runs at placement:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import {
|
|
420
|
+
calculateCoefficientWrapper,
|
|
421
|
+
type CoefficientInput,
|
|
422
|
+
} from '@taphubhq/sdk-core';
|
|
423
|
+
|
|
424
|
+
const coefficient = calculateCoefficientWrapper({
|
|
425
|
+
time1, time2, price1, price2, // the cell you are bidding on
|
|
426
|
+
candleTime, candleClose, volatility, // the live candle
|
|
427
|
+
coefMults, minCoef, // from pair.config
|
|
428
|
+
cellSizeTime, candleSize, // from pair.config
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
A return of `Number.POSITIVE_INFINITY` means the cell cannot be priced right now —
|
|
433
|
+
do not pass it to `placeBid`.
|
|
434
|
+
|
|
435
|
+
The server stays authoritative: a value that disagrees beyond your `slippage`
|
|
436
|
+
tolerance is rejected with `Bid_CoefficientMismatch` (see
|
|
437
|
+
[Slippage error fallback](#slippage-error-fallback)).
|
|
438
|
+
|
|
439
|
+
**Candle-interval variants.** `candleSize` is part of `CoefficientInput` but does not
|
|
440
|
+
enter the coefficient: the cell width is measured in bars (`cellSizeTime`), and
|
|
441
|
+
`calculateProbHit` already prices the real seconds window, so dividing by
|
|
442
|
+
`candleSize` a second time would double-penalise the 1m/5m variants by roughly
|
|
443
|
+
60×/300× (`bid-260703`). Pass the value from `pair.config` anyway —
|
|
444
|
+
`computeBaseline` uses it.
|
|
445
|
+
|
|
446
|
+
### Deprecated helpers
|
|
447
|
+
|
|
448
|
+
`calculateProbWin` and `calculateProbWin_v2` are **deprecated**. They compute a
|
|
449
|
+
one-sided exceedance probability and ignore `price1`, so they do not match the model
|
|
450
|
+
placement validates against. A coefficient derived from them can pass the
|
|
451
|
+
server's one-directional slippage check and lock a lower payout than the cell is
|
|
452
|
+
worth. Use `calculateCoefficientWrapper`. They remain exported, unchanged, for
|
|
453
|
+
existing callers.
|
|
454
|
+
|
|
455
|
+
The lower-level pieces — `calculateProbHit`, `roundCoefToSignificantDigits`,
|
|
456
|
+
`computeBaseline`, `normalCDF`, `normalPDF`, `errorFunction`, `adaptiveSimpson` —
|
|
457
|
+
are also exported for consumers that need to build on them directly.
|
|
458
|
+
|
|
117
459
|
## License
|
|
118
460
|
|
|
119
461
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -52,8 +52,11 @@ __export(index_exports, {
|
|
|
52
52
|
UserModule: () => UserModule,
|
|
53
53
|
adaptiveSimpson: () => adaptiveSimpson,
|
|
54
54
|
autoDetectStorage: () => autoDetectStorage,
|
|
55
|
+
calculateCoefficientWrapper: () => calculateCoefficientWrapper,
|
|
56
|
+
calculateProbHit: () => calculateProbHit,
|
|
55
57
|
calculateProbWin: () => calculateProbWin,
|
|
56
58
|
calculateProbWin_v2: () => calculateProbWin_v2,
|
|
59
|
+
computeBaseline: () => computeBaseline,
|
|
57
60
|
errorFunction: () => errorFunction,
|
|
58
61
|
isCancelled: () => isCancelled,
|
|
59
62
|
isLoss: () => isLoss,
|
|
@@ -63,7 +66,8 @@ __export(index_exports, {
|
|
|
63
66
|
normalCDF: () => normalCDF,
|
|
64
67
|
normalPDF: () => normalPDF,
|
|
65
68
|
normaliseLang: () => normaliseLang,
|
|
66
|
-
pairIdFromBidResultTopic: () => pairIdFromBidResultTopic
|
|
69
|
+
pairIdFromBidResultTopic: () => pairIdFromBidResultTopic,
|
|
70
|
+
roundCoefToSignificantDigits: () => roundCoefToSignificantDigits
|
|
67
71
|
});
|
|
68
72
|
module.exports = __toCommonJS(index_exports);
|
|
69
73
|
|
|
@@ -3251,6 +3255,115 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3251
3255
|
const prob = 1 - normalCDF(z);
|
|
3252
3256
|
return Math.max(0, Math.min(1, prob));
|
|
3253
3257
|
}
|
|
3258
|
+
function simpsonForHit(f, a, b, eps = 3e-5, maxDepth = 14) {
|
|
3259
|
+
function simpsonRule(a2, b2) {
|
|
3260
|
+
const c = (a2 + b2) / 2;
|
|
3261
|
+
const h = b2 - a2;
|
|
3262
|
+
return h / 6 * (f(a2) + 4 * f(c) + f(b2));
|
|
3263
|
+
}
|
|
3264
|
+
function recurse(a2, b2, eps2, whole2, depth) {
|
|
3265
|
+
const c = (a2 + b2) / 2;
|
|
3266
|
+
const left = simpsonRule(a2, c);
|
|
3267
|
+
const right = simpsonRule(c, b2);
|
|
3268
|
+
if (depth >= maxDepth || Math.abs(left + right - whole2) <= 15 * eps2) {
|
|
3269
|
+
return left + right + (left + right - whole2) / 15;
|
|
3270
|
+
}
|
|
3271
|
+
return recurse(a2, c, eps2 / 2, left, depth + 1) + recurse(c, b2, eps2 / 2, right, depth + 1);
|
|
3272
|
+
}
|
|
3273
|
+
const whole = simpsonRule(a, b);
|
|
3274
|
+
return recurse(a, b, eps, whole, 0);
|
|
3275
|
+
}
|
|
3276
|
+
function calculateProbHit(time1, time2, price1, price2, creationTime, currentPrice, volatility) {
|
|
3277
|
+
if (volatility <= 0 || currentPrice <= 0) return 0;
|
|
3278
|
+
const sigma = volatility;
|
|
3279
|
+
const L = Math.log(price1 / currentPrice);
|
|
3280
|
+
const U = Math.log(price2 / currentPrice);
|
|
3281
|
+
let T1 = time1 - creationTime;
|
|
3282
|
+
if (T1 < 0) T1 = 0;
|
|
3283
|
+
const dT = time2 - time1;
|
|
3284
|
+
if (dT <= 0) return 0;
|
|
3285
|
+
let probInside;
|
|
3286
|
+
if (T1 <= 0) {
|
|
3287
|
+
probInside = currentPrice >= price1 && currentPrice <= price2 ? 1 : 0;
|
|
3288
|
+
} else {
|
|
3289
|
+
const sqrtT12 = Math.sqrt(T1);
|
|
3290
|
+
const sigSqrtT12 = sigma * sqrtT12;
|
|
3291
|
+
if (sigSqrtT12 < 1e-12) {
|
|
3292
|
+
probInside = currentPrice >= price1 && currentPrice <= price2 ? 1 : 0;
|
|
3293
|
+
} else {
|
|
3294
|
+
probInside = normalCDF(U / sigSqrtT12) - normalCDF(L / sigSqrtT12);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
if (T1 <= 0) {
|
|
3298
|
+
return Math.max(0, Math.min(1, probInside));
|
|
3299
|
+
}
|
|
3300
|
+
const sqrtT1 = Math.sqrt(T1);
|
|
3301
|
+
const sigSqrtT1 = sigma * sqrtT1;
|
|
3302
|
+
const sqrtRatio = Math.sqrt(T1 / dT);
|
|
3303
|
+
const sigSqrtDT = sigma * Math.sqrt(dT);
|
|
3304
|
+
const zU = U / sigSqrtT1;
|
|
3305
|
+
const integrandAbove = (z) => {
|
|
3306
|
+
return 2 * normalCDF(U / sigSqrtDT - z * sqrtRatio) * normalPDF(z);
|
|
3307
|
+
};
|
|
3308
|
+
const pFromAbove = simpsonForHit(integrandAbove, zU, zU + 7, 3e-5, 14);
|
|
3309
|
+
const zL = L / sigSqrtT1;
|
|
3310
|
+
const integrandBelow = (z) => {
|
|
3311
|
+
return 2 * normalCDF(z * sqrtRatio - L / sigSqrtDT) * normalPDF(z);
|
|
3312
|
+
};
|
|
3313
|
+
const pFromBelow = simpsonForHit(integrandBelow, zL - 7, zL, 3e-5, 14);
|
|
3314
|
+
const result = probInside + pFromAbove + pFromBelow;
|
|
3315
|
+
return Math.max(0, Math.min(1, result));
|
|
3316
|
+
}
|
|
3317
|
+
function calculateCoefficientWrapper(params) {
|
|
3318
|
+
const {
|
|
3319
|
+
time1,
|
|
3320
|
+
time2,
|
|
3321
|
+
price1,
|
|
3322
|
+
price2,
|
|
3323
|
+
candleTime,
|
|
3324
|
+
candleClose,
|
|
3325
|
+
volatility,
|
|
3326
|
+
coefMults,
|
|
3327
|
+
cellSizeTime,
|
|
3328
|
+
minCoef
|
|
3329
|
+
} = params;
|
|
3330
|
+
const currentPrice = candleClose;
|
|
3331
|
+
const creationTime = candleTime;
|
|
3332
|
+
const probability = calculateProbHit(
|
|
3333
|
+
time1,
|
|
3334
|
+
time2,
|
|
3335
|
+
price1,
|
|
3336
|
+
price2,
|
|
3337
|
+
creationTime,
|
|
3338
|
+
currentPrice,
|
|
3339
|
+
volatility
|
|
3340
|
+
);
|
|
3341
|
+
if (probability <= 0) return Number.POSITIVE_INFINITY;
|
|
3342
|
+
const rawCoef = 1 / probability;
|
|
3343
|
+
const rawIndex = rawCoef < 1 ? 0 : Math.floor(Math.log2(rawCoef));
|
|
3344
|
+
const coefMultIndex = Math.min(rawIndex, coefMults.length - 1);
|
|
3345
|
+
const multiplier = coefMults[coefMultIndex] || 1;
|
|
3346
|
+
const timeRatio = cellSizeTime;
|
|
3347
|
+
const adjustedProb = probability * (timeRatio / 5);
|
|
3348
|
+
if (adjustedProb <= 0) return Number.POSITIVE_INFINITY;
|
|
3349
|
+
const floor = typeof minCoef === "number" && minCoef > 0 ? minCoef : 1;
|
|
3350
|
+
const finalCoef = Math.max(floor, multiplier / adjustedProb);
|
|
3351
|
+
return roundCoefToSignificantDigits(finalCoef);
|
|
3352
|
+
}
|
|
3353
|
+
function roundCoefToSignificantDigits(value) {
|
|
3354
|
+
if (!Number.isFinite(value) || value <= 0) return value;
|
|
3355
|
+
const magnitude = Math.floor(Math.log10(value));
|
|
3356
|
+
const leadingDigit = Math.floor(value / 10 ** magnitude);
|
|
3357
|
+
const sigDigits = leadingDigit === 1 ? 3 : 2;
|
|
3358
|
+
const factor = 10 ** (sigDigits - magnitude - 1);
|
|
3359
|
+
return Math.round(value * factor) / factor;
|
|
3360
|
+
}
|
|
3361
|
+
function computeBaseline(candleClose, candleTimeSec, cellSizeValue, cellSizeTime, candleSize) {
|
|
3362
|
+
const baseline = Math.floor(candleClose / cellSizeValue) * cellSizeValue;
|
|
3363
|
+
const cellSizeTimeSec = candleSize * cellSizeTime;
|
|
3364
|
+
const baselineTime = Math.floor(candleTimeSec / cellSizeTimeSec) * cellSizeTimeSec + 0.5;
|
|
3365
|
+
return { baseline, baselineTime };
|
|
3366
|
+
}
|
|
3254
3367
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3255
3368
|
0 && (module.exports = {
|
|
3256
3369
|
AgencyPairModule,
|
|
@@ -3275,8 +3388,11 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3275
3388
|
UserModule,
|
|
3276
3389
|
adaptiveSimpson,
|
|
3277
3390
|
autoDetectStorage,
|
|
3391
|
+
calculateCoefficientWrapper,
|
|
3392
|
+
calculateProbHit,
|
|
3278
3393
|
calculateProbWin,
|
|
3279
3394
|
calculateProbWin_v2,
|
|
3395
|
+
computeBaseline,
|
|
3280
3396
|
errorFunction,
|
|
3281
3397
|
isCancelled,
|
|
3282
3398
|
isLoss,
|
|
@@ -3286,5 +3402,6 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3286
3402
|
normalCDF,
|
|
3287
3403
|
normalPDF,
|
|
3288
3404
|
normaliseLang,
|
|
3289
|
-
pairIdFromBidResultTopic
|
|
3405
|
+
pairIdFromBidResultTopic,
|
|
3406
|
+
roundCoefToSignificantDigits
|
|
3290
3407
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -1429,7 +1429,75 @@ declare function errorFunction(x: number): number;
|
|
|
1429
1429
|
declare function normalCDF(x: number): number;
|
|
1430
1430
|
declare function normalPDF(x: number): number;
|
|
1431
1431
|
declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number, tolerance?: number, maxDepth?: number): number;
|
|
1432
|
+
/**
|
|
1433
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1434
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1435
|
+
* `placeBid`.
|
|
1436
|
+
*
|
|
1437
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1438
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1439
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1440
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1441
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1442
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1443
|
+
*
|
|
1444
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1445
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1446
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1447
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1448
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1449
|
+
*
|
|
1450
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1451
|
+
*/
|
|
1432
1452
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1453
|
+
/**
|
|
1454
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1455
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1456
|
+
* `placeBid`.
|
|
1457
|
+
*
|
|
1458
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1459
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1460
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1461
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1462
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1463
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1464
|
+
*
|
|
1465
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1466
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1467
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1468
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1469
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1470
|
+
*
|
|
1471
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1472
|
+
*/
|
|
1433
1473
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1474
|
+
interface CoefficientInput {
|
|
1475
|
+
time1: number;
|
|
1476
|
+
time2: number;
|
|
1477
|
+
price1: number;
|
|
1478
|
+
price2: number;
|
|
1479
|
+
candleTime: number;
|
|
1480
|
+
candleClose: number;
|
|
1481
|
+
volatility: number;
|
|
1482
|
+
coefMults: number[];
|
|
1483
|
+
cellSizeTime: number;
|
|
1484
|
+
candleSize: number;
|
|
1485
|
+
minCoef?: number;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* First-passage hitting probability: chance the price visits [price1, price2]
|
|
1489
|
+
* at any point during [time1, time2], given GBM dynamics.
|
|
1490
|
+
*
|
|
1491
|
+
* P_hit = P_inside + P_from_above + P_from_below
|
|
1492
|
+
*
|
|
1493
|
+
* Matches Go backend algorithm.
|
|
1494
|
+
*/
|
|
1495
|
+
declare function calculateProbHit(time1: number, time2: number, price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1496
|
+
declare function calculateCoefficientWrapper(params: CoefficientInput): number;
|
|
1497
|
+
declare function roundCoefToSignificantDigits(value: number): number;
|
|
1498
|
+
declare function computeBaseline(candleClose: number, candleTimeSec: number, cellSizeValue: number, cellSizeTime: number, candleSize: number): {
|
|
1499
|
+
baseline: number;
|
|
1500
|
+
baselineTime: number;
|
|
1501
|
+
};
|
|
1434
1502
|
|
|
1435
|
-
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic };
|
|
1503
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, roundCoefToSignificantDigits };
|
package/dist/index.d.ts
CHANGED
|
@@ -1429,7 +1429,75 @@ declare function errorFunction(x: number): number;
|
|
|
1429
1429
|
declare function normalCDF(x: number): number;
|
|
1430
1430
|
declare function normalPDF(x: number): number;
|
|
1431
1431
|
declare function adaptiveSimpson(f: (x: number) => number, a: number, b: number, tolerance?: number, maxDepth?: number): number;
|
|
1432
|
+
/**
|
|
1433
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1434
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1435
|
+
* `placeBid`.
|
|
1436
|
+
*
|
|
1437
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1438
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1439
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1440
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1441
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1442
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1443
|
+
*
|
|
1444
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1445
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1446
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1447
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1448
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1449
|
+
*
|
|
1450
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1451
|
+
*/
|
|
1432
1452
|
declare function calculateProbWin(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1453
|
+
/**
|
|
1454
|
+
* @deprecated Does NOT match the model grid-api validates a bid against. Use
|
|
1455
|
+
* {@link calculateCoefficientWrapper} to derive the `coefficient` argument for
|
|
1456
|
+
* `placeBid`.
|
|
1457
|
+
*
|
|
1458
|
+
* This computes a one-sided exceedance probability — roughly "will the price end up
|
|
1459
|
+
* past `price2`" — and takes `price1` without using it. The game pays out when the
|
|
1460
|
+
* price *visits the band* `[price1, price2]`, which is a first-passage probability;
|
|
1461
|
+
* {@link calculateProbHit} computes that one. Measured against grid-api's reference
|
|
1462
|
+
* inputs, this function returns only 1.0, 0.5 or 0.0 and does not move at all when
|
|
1463
|
+
* `price1` goes from 2869.75 to 2800.00.
|
|
1464
|
+
*
|
|
1465
|
+
* The consequence is not a rejected bid. `placeBid`'s slippage check is
|
|
1466
|
+
* one-directional — it rejects a coefficient *higher* than the server's, never a
|
|
1467
|
+
* lower one — so a coefficient derived from this function (e.g. 1.0 where the server
|
|
1468
|
+
* computes 6.1) is accepted and locked, and the player is paid at the lower
|
|
1469
|
+
* coefficient on a win, with no error raised anywhere.
|
|
1470
|
+
*
|
|
1471
|
+
* Retained unchanged because it is published API (TH-491, Add-Deprecate-Remove).
|
|
1472
|
+
*/
|
|
1433
1473
|
declare function calculateProbWin_v2(time1: number, time2: number, _price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1474
|
+
interface CoefficientInput {
|
|
1475
|
+
time1: number;
|
|
1476
|
+
time2: number;
|
|
1477
|
+
price1: number;
|
|
1478
|
+
price2: number;
|
|
1479
|
+
candleTime: number;
|
|
1480
|
+
candleClose: number;
|
|
1481
|
+
volatility: number;
|
|
1482
|
+
coefMults: number[];
|
|
1483
|
+
cellSizeTime: number;
|
|
1484
|
+
candleSize: number;
|
|
1485
|
+
minCoef?: number;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* First-passage hitting probability: chance the price visits [price1, price2]
|
|
1489
|
+
* at any point during [time1, time2], given GBM dynamics.
|
|
1490
|
+
*
|
|
1491
|
+
* P_hit = P_inside + P_from_above + P_from_below
|
|
1492
|
+
*
|
|
1493
|
+
* Matches Go backend algorithm.
|
|
1494
|
+
*/
|
|
1495
|
+
declare function calculateProbHit(time1: number, time2: number, price1: number, price2: number, creationTime: number, currentPrice: number, volatility: number): number;
|
|
1496
|
+
declare function calculateCoefficientWrapper(params: CoefficientInput): number;
|
|
1497
|
+
declare function roundCoefToSignificantDigits(value: number): number;
|
|
1498
|
+
declare function computeBaseline(candleClose: number, candleTimeSec: number, cellSizeValue: number, cellSizeTime: number, candleSize: number): {
|
|
1499
|
+
baseline: number;
|
|
1500
|
+
baselineTime: number;
|
|
1501
|
+
};
|
|
1434
1502
|
|
|
1435
|
-
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateProbWin, calculateProbWin_v2, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic };
|
|
1503
|
+
export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, roundCoefToSignificantDigits };
|
package/dist/index.js
CHANGED
|
@@ -3182,6 +3182,115 @@ function calculateProbWin_v2(time1, time2, _price1, price2, creationTime, curren
|
|
|
3182
3182
|
const prob = 1 - normalCDF(z);
|
|
3183
3183
|
return Math.max(0, Math.min(1, prob));
|
|
3184
3184
|
}
|
|
3185
|
+
function simpsonForHit(f, a, b, eps = 3e-5, maxDepth = 14) {
|
|
3186
|
+
function simpsonRule(a2, b2) {
|
|
3187
|
+
const c = (a2 + b2) / 2;
|
|
3188
|
+
const h = b2 - a2;
|
|
3189
|
+
return h / 6 * (f(a2) + 4 * f(c) + f(b2));
|
|
3190
|
+
}
|
|
3191
|
+
function recurse(a2, b2, eps2, whole2, depth) {
|
|
3192
|
+
const c = (a2 + b2) / 2;
|
|
3193
|
+
const left = simpsonRule(a2, c);
|
|
3194
|
+
const right = simpsonRule(c, b2);
|
|
3195
|
+
if (depth >= maxDepth || Math.abs(left + right - whole2) <= 15 * eps2) {
|
|
3196
|
+
return left + right + (left + right - whole2) / 15;
|
|
3197
|
+
}
|
|
3198
|
+
return recurse(a2, c, eps2 / 2, left, depth + 1) + recurse(c, b2, eps2 / 2, right, depth + 1);
|
|
3199
|
+
}
|
|
3200
|
+
const whole = simpsonRule(a, b);
|
|
3201
|
+
return recurse(a, b, eps, whole, 0);
|
|
3202
|
+
}
|
|
3203
|
+
function calculateProbHit(time1, time2, price1, price2, creationTime, currentPrice, volatility) {
|
|
3204
|
+
if (volatility <= 0 || currentPrice <= 0) return 0;
|
|
3205
|
+
const sigma = volatility;
|
|
3206
|
+
const L = Math.log(price1 / currentPrice);
|
|
3207
|
+
const U = Math.log(price2 / currentPrice);
|
|
3208
|
+
let T1 = time1 - creationTime;
|
|
3209
|
+
if (T1 < 0) T1 = 0;
|
|
3210
|
+
const dT = time2 - time1;
|
|
3211
|
+
if (dT <= 0) return 0;
|
|
3212
|
+
let probInside;
|
|
3213
|
+
if (T1 <= 0) {
|
|
3214
|
+
probInside = currentPrice >= price1 && currentPrice <= price2 ? 1 : 0;
|
|
3215
|
+
} else {
|
|
3216
|
+
const sqrtT12 = Math.sqrt(T1);
|
|
3217
|
+
const sigSqrtT12 = sigma * sqrtT12;
|
|
3218
|
+
if (sigSqrtT12 < 1e-12) {
|
|
3219
|
+
probInside = currentPrice >= price1 && currentPrice <= price2 ? 1 : 0;
|
|
3220
|
+
} else {
|
|
3221
|
+
probInside = normalCDF(U / sigSqrtT12) - normalCDF(L / sigSqrtT12);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
if (T1 <= 0) {
|
|
3225
|
+
return Math.max(0, Math.min(1, probInside));
|
|
3226
|
+
}
|
|
3227
|
+
const sqrtT1 = Math.sqrt(T1);
|
|
3228
|
+
const sigSqrtT1 = sigma * sqrtT1;
|
|
3229
|
+
const sqrtRatio = Math.sqrt(T1 / dT);
|
|
3230
|
+
const sigSqrtDT = sigma * Math.sqrt(dT);
|
|
3231
|
+
const zU = U / sigSqrtT1;
|
|
3232
|
+
const integrandAbove = (z) => {
|
|
3233
|
+
return 2 * normalCDF(U / sigSqrtDT - z * sqrtRatio) * normalPDF(z);
|
|
3234
|
+
};
|
|
3235
|
+
const pFromAbove = simpsonForHit(integrandAbove, zU, zU + 7, 3e-5, 14);
|
|
3236
|
+
const zL = L / sigSqrtT1;
|
|
3237
|
+
const integrandBelow = (z) => {
|
|
3238
|
+
return 2 * normalCDF(z * sqrtRatio - L / sigSqrtDT) * normalPDF(z);
|
|
3239
|
+
};
|
|
3240
|
+
const pFromBelow = simpsonForHit(integrandBelow, zL - 7, zL, 3e-5, 14);
|
|
3241
|
+
const result = probInside + pFromAbove + pFromBelow;
|
|
3242
|
+
return Math.max(0, Math.min(1, result));
|
|
3243
|
+
}
|
|
3244
|
+
function calculateCoefficientWrapper(params) {
|
|
3245
|
+
const {
|
|
3246
|
+
time1,
|
|
3247
|
+
time2,
|
|
3248
|
+
price1,
|
|
3249
|
+
price2,
|
|
3250
|
+
candleTime,
|
|
3251
|
+
candleClose,
|
|
3252
|
+
volatility,
|
|
3253
|
+
coefMults,
|
|
3254
|
+
cellSizeTime,
|
|
3255
|
+
minCoef
|
|
3256
|
+
} = params;
|
|
3257
|
+
const currentPrice = candleClose;
|
|
3258
|
+
const creationTime = candleTime;
|
|
3259
|
+
const probability = calculateProbHit(
|
|
3260
|
+
time1,
|
|
3261
|
+
time2,
|
|
3262
|
+
price1,
|
|
3263
|
+
price2,
|
|
3264
|
+
creationTime,
|
|
3265
|
+
currentPrice,
|
|
3266
|
+
volatility
|
|
3267
|
+
);
|
|
3268
|
+
if (probability <= 0) return Number.POSITIVE_INFINITY;
|
|
3269
|
+
const rawCoef = 1 / probability;
|
|
3270
|
+
const rawIndex = rawCoef < 1 ? 0 : Math.floor(Math.log2(rawCoef));
|
|
3271
|
+
const coefMultIndex = Math.min(rawIndex, coefMults.length - 1);
|
|
3272
|
+
const multiplier = coefMults[coefMultIndex] || 1;
|
|
3273
|
+
const timeRatio = cellSizeTime;
|
|
3274
|
+
const adjustedProb = probability * (timeRatio / 5);
|
|
3275
|
+
if (adjustedProb <= 0) return Number.POSITIVE_INFINITY;
|
|
3276
|
+
const floor = typeof minCoef === "number" && minCoef > 0 ? minCoef : 1;
|
|
3277
|
+
const finalCoef = Math.max(floor, multiplier / adjustedProb);
|
|
3278
|
+
return roundCoefToSignificantDigits(finalCoef);
|
|
3279
|
+
}
|
|
3280
|
+
function roundCoefToSignificantDigits(value) {
|
|
3281
|
+
if (!Number.isFinite(value) || value <= 0) return value;
|
|
3282
|
+
const magnitude = Math.floor(Math.log10(value));
|
|
3283
|
+
const leadingDigit = Math.floor(value / 10 ** magnitude);
|
|
3284
|
+
const sigDigits = leadingDigit === 1 ? 3 : 2;
|
|
3285
|
+
const factor = 10 ** (sigDigits - magnitude - 1);
|
|
3286
|
+
return Math.round(value * factor) / factor;
|
|
3287
|
+
}
|
|
3288
|
+
function computeBaseline(candleClose, candleTimeSec, cellSizeValue, cellSizeTime, candleSize) {
|
|
3289
|
+
const baseline = Math.floor(candleClose / cellSizeValue) * cellSizeValue;
|
|
3290
|
+
const cellSizeTimeSec = candleSize * cellSizeTime;
|
|
3291
|
+
const baselineTime = Math.floor(candleTimeSec / cellSizeTimeSec) * cellSizeTimeSec + 0.5;
|
|
3292
|
+
return { baseline, baselineTime };
|
|
3293
|
+
}
|
|
3185
3294
|
export {
|
|
3186
3295
|
AgencyPairModule,
|
|
3187
3296
|
AuthModule,
|
|
@@ -3205,8 +3314,11 @@ export {
|
|
|
3205
3314
|
UserModule,
|
|
3206
3315
|
adaptiveSimpson,
|
|
3207
3316
|
autoDetectStorage,
|
|
3317
|
+
calculateCoefficientWrapper,
|
|
3318
|
+
calculateProbHit,
|
|
3208
3319
|
calculateProbWin,
|
|
3209
3320
|
calculateProbWin_v2,
|
|
3321
|
+
computeBaseline,
|
|
3210
3322
|
errorFunction,
|
|
3211
3323
|
isCancelled,
|
|
3212
3324
|
isLoss,
|
|
@@ -3216,5 +3328,6 @@ export {
|
|
|
3216
3328
|
normalCDF,
|
|
3217
3329
|
normalPDF,
|
|
3218
3330
|
normaliseLang,
|
|
3219
|
-
pairIdFromBidResultTopic
|
|
3331
|
+
pairIdFromBidResultTopic,
|
|
3332
|
+
roundCoefToSignificantDigits
|
|
3220
3333
|
};
|