marginpad 2.9.1 → 3.0.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/README.md CHANGED
@@ -37,6 +37,31 @@ const { position } = await mp.open({ symbol: 'BTC', side: 'long', margin_usd: 10
37
37
  mp.stream(ev => console.log(ev.type, ev.data)); // positions pushed on change, prices each tick
38
38
  ```
39
39
 
40
+ ## The simulation is yours to set
41
+ <!-- sdk-sim: generated, do not hand-edit -->
42
+
43
+ Eighteen fields and eight presets, so the paper venue can be the one you will actually trade. The defaults are deliberately frictionless, and nothing you have already built moves until you change them.
44
+
45
+ ```python
46
+ mp.sim() # your profile, every field with its range, the presets
47
+ mp.sim(preset="realistic") # the honest middle, in one call
48
+ mp.sim(preset="bybit") # Bybit's published taker fee and maintenance margin
49
+ mp.sim(preset="brutal") # refusals, partial fills, a second of latency
50
+ mp.sim(start_balance_usd=2500, margin_enforced=True, slippage_bps=8, latency_ms=250)
51
+ mp.sim(reset=True) # back to the defaults
52
+ ```
53
+
54
+ ```js
55
+ await mp.sim({ preset: 'realistic' });
56
+ await mp.sim({ start_balance_usd: 2500, margin_enforced: true, reject_rate_pct: 2 });
57
+ ```
58
+
59
+ The **book** (`start_balance_usd`, `margin_enforced`, `leverage_max`, `max_notional_usd`), the **fill** (`slippage`, `slippage_bps`, `impact_bps_per_100k`, `exit_slippage`, `latency_ms`, `partial_fills`, `min_fill_pct`, `reject_rate_pct`), the **cost** (`taker_bps`, `funding`, `funding_mult`) and the **liquidation** (`margin_venue`, `margin_tiers`, `mmr_pct`).
60
+
61
+ Out-of-range values are refused, never clamped, and a position keeps the simulation it was filled under for life. `margin_enforced` turns the balance into a real budget: size past it and the open comes back 402, which is the failure a live bot has to handle and a frictionless simulator never shows you. `reject_rate_pct` is the one a retry loop is worth writing for.
62
+
63
+ Every field with its range: https://marginpad.io/trading-api/#sim
64
+
40
65
  ## Examples
41
66
 
42
67
  - `examples/agent_loop.py`: a signal loop that opens, manages and closes positions once a minute without polling storms.
@@ -44,13 +69,23 @@ mp.stream(ev => console.log(ev.type, ev.data)); // positions pushed
44
69
  - `examples/mcp.md`: let Claude Desktop or Cursor trade on your paper account through the MCP server.
45
70
 
46
71
  ## Limits
72
+ <!-- sdk-limits: generated from API_PLANS, do not hand-edit -->
73
+
74
+ | Plan | Requests/min | Keys | Open positions | Books | Webhooks |
75
+ |---|---|---|---|---|---|
76
+ | Free $0 | 120 | 3 | 50 | 1 | - |
77
+ | Pro $39 | 600 | 10 | 200 | 5 | 3 |
78
+ | Max $99 | 2000 | 30 | 500 | 20 | 15 |
79
+ | Business $199 | 5000 | 100 | 1000 | 50 | 50 |
80
+
81
+ An API plan is its own product: a MarginPad Premium subscription buys nothing here, and an API plan buys nothing on the site. The whole engine, the simulation, several books per key, the replay, the WebSocket stream and every market-data endpoint are free - only webhooks and the AI read are gated. Trade history is 30 days on Free and 90 on a paid plan.
82
+
83
+ Measured in production: the busiest bot peaks at 66 requests a minute against the Free ceiling of 120, and no key has ever been rate-limited on any plan. Choose on keys and history, not on throughput.
47
84
 
48
- Free: 120 requests/minute per key, 3 keys, 50 open positions. Premium: 600/minute, 10 keys, 200 open positions.
49
- Every response carries `X-RateLimit-Limit / Remaining / Reset`; on 429 both clients wait `Retry-After` and retry once.
50
- Out-of-range input is refused with a named error (`leverage_max` carries the market's cap), never silently clamped.
85
+ Every response carries `X-RateLimit-Limit / Remaining / Reset`; on 429 both clients wait `Retry-After` and retry once. Out-of-range input is refused with a named error (`leverage_max` carries the market's cap), never silently clamped.
51
86
 
52
87
  ## Honesty note
53
88
 
54
- This is a simulator. Fees, funding and liquidation math mirror real exchanges; there is no order book, so slippage is modelled, not measured on depth. A bot that passes here still needs a small live test. Educational use, not financial advice.
89
+ This is a simulator. Fees, funding and liquidation maths mirror real exchanges and every fill condition above is yours to set - but there is no order book behind `impact_bps_per_100k`, so market impact is modelled rather than measured on depth, and a stop, target or liquidation fills at its own level without paying the crossing cost. A bot that passes here still needs a small live test. Educational use, not financial advice.
55
90
 
56
91
  MIT licensed.
package/index.d.ts CHANGED
@@ -1,24 +1,61 @@
1
1
  export interface Position { id: string; symbol: string; side: 'long' | 'short'; entry_price: number; margin_usd: number; leverage: number; qty: number; liq_price: number; sl: number | null; tp: number | null; status: 'open' | 'closed' | 'liquidated'; opened_ts: number; mark_price?: number; unrealized_pnl_usd?: number; exit_price?: number | null; pnl_usd?: number | null; closed_ts?: number | null; trail_pct?: number; fee_rate_pct?: number; fee_venue?: string | null; }
2
- export interface OpenParams { symbol: string; side: 'long' | 'short'; margin_usd: number; leverage?: number; sl?: number; tp?: number; trail_pct?: number; type?: 'market' | 'limit' | 'stop'; limit_price?: number; client_order_id?: string; fee_venue?: string | null; dry_run?: boolean; }
2
+ export interface OpenParams { symbol: string; side: 'long' | 'short' | 'buy' | 'sell'; margin_usd: number; leverage?: number; sl?: number; tp?: number; trail_pct?: number; type?: 'market' | 'limit' | 'stop'; limit_price?: number; client_order_id?: string; fee_venue?: string | null; dry_run?: boolean;
3
+ /** Override the account's simulation profile for THIS fill only. Writes nothing. */
4
+ slippage?: boolean; slippage_bps?: number; impact_bps_per_100k?: number; margin_tiers?: boolean; mmr_pct?: number; }
5
+ /** What actually filled, on v2 opens. position.margin_usd is already the filled size; this says what you asked for. */
6
+ export interface FillInfo { requested_margin_usd: number; filled_margin_usd: number; fill_pct: number; partial: boolean; slippage_pct: number; exit_slippage: boolean; latency_ms: number; taker_fee_pct: number; maintenance_margin_pct: number; maintenance_margin_from: string; }
7
+ /** The eight ready-made simulations. A preset starts from the defaults, so it is a clean slate plus that configuration. */
8
+ export type SimPreset = 'frictionless' | 'realistic' | 'binance' | 'bybit' | 'okx' | 'hyperliquid' | 'thin_book' | 'brutal';
9
+ /**
10
+ * The whole paper-trading simulation. Send any subset; out-of-range values are REFUSED, never clamped.
11
+ * The three nullable fields take null to mean "use the measured table / the venue", not zero.
12
+ */
13
+ export interface SimProfile {
14
+ preset?: SimPreset; reset?: boolean;
15
+ /** the book */
16
+ start_balance_usd?: number; margin_enforced?: boolean; leverage_max?: number; max_notional_usd?: number;
17
+ /** the fill */
18
+ slippage?: boolean; slippage_bps?: number | null; impact_bps_per_100k?: number; exit_slippage?: boolean;
19
+ latency_ms?: number; partial_fills?: boolean; min_fill_pct?: number; reject_rate_pct?: number;
20
+ /** the cost */
21
+ taker_bps?: number | null; funding?: boolean; funding_mult?: number;
22
+ /** the liquidation */
23
+ margin_venue?: string; margin_tiers?: boolean; mmr_pct?: number | null;
24
+ }
3
25
  export class MarginPadError extends Error { code: string; status: number; body: any; }
4
26
  export class MarginPad {
27
+ static SIM_PRESETS: SimPreset[];
5
28
  constructor(key?: string, opts?: { baseUrl?: string; autoRetry?: boolean; timeoutMs?: number });
6
29
  price(symbol: string): Promise<{ symbol: string; price: number; change_24h_pct: number | null; ts: number }>;
30
+ prices(): Promise<any>;
7
31
  klines(symbol: string, interval?: string, end?: number): Promise<any[]>;
8
- time(): Promise<{ server_time_ms: number; server_time_iso: string }>;
9
- open(p: OpenParams): Promise<{ position?: Position; order?: any; dry_run?: boolean }>;
32
+ // NOTE: the method is serverTime(). This file declared a `time()` that the client has never had - a
33
+ // declaration for a method that does not exist is worse than no declaration, so it is gone.
34
+ serverTime(): Promise<{ server_time_ms: number; server_time_iso: string }>;
35
+ open(p: OpenParams): Promise<{ position?: Position; order?: any; dry_run?: boolean; fill?: FillInfo }>;
36
+ limitOrder(p: OpenParams): Promise<any>;
37
+ stopOrder(p: OpenParams & { stop_price?: number }): Promise<any>;
10
38
  close(id: string, opts?: { pct?: number; symbol?: string; client_order_id?: string }): Promise<{ position: Position }>;
11
39
  closeAll(): Promise<{ closed: number; positions: Position[] }>;
12
40
  sltp(id: string, p: { sl?: number | null; tp?: number | null; trail_pct?: number | null }): Promise<{ position: Position }>;
13
- positions(): Promise<{ positions: Position[] }>;
41
+ positions(opts?: { status?: 'open' | 'closed'; since?: number; useEtag?: boolean }): Promise<{ positions: Position[] } | null>;
14
42
  orders(): Promise<{ orders: any[] }>;
15
43
  cancelOrder(id: string): Promise<{ order_id: string; status: string }>;
16
44
  modifyOrder(id: string, p: any): Promise<any>;
17
- trades(opts?: { limit?: number; before?: number }): Promise<{ trades: Position[]; next_before: number | null }>;
45
+ trades(limit?: number, before?: number): Promise<{ trades: Position[]; next_before: number | null }>;
18
46
  account(): Promise<any>;
19
47
  balance(): Promise<any>;
20
- markets(): Promise<any>;
21
- fees(): Promise<any>;
48
+ equity(days?: number, stepMin?: number): Promise<any>;
49
+ reset(): Promise<any>;
50
+ accounts(): Promise<any>;
51
+ markets(assetClass?: string): Promise<any>;
52
+ /** The fee schedule. fees() reads, fees('binance') sets the account default, fees('') goes back to ours. */
53
+ fees(venue?: string): Promise<any>;
54
+ /** Three switches of the simulation, kept for compatibility. sim() is all of it. */
55
+ realism(o?: { slippage?: boolean; margin_tiers?: boolean; margin_venue?: string }): Promise<any>;
56
+ /** The whole simulation: read with no argument, set with any subset or a preset. */
57
+ sim(o?: SimProfile): Promise<any>;
58
+ /** The trading report. `costs` (fees, funding, gross vs net, kept_pct) is on every plan. */
22
59
  report(days?: number): Promise<any>;
23
60
  usage(): Promise<any>;
24
61
  webhooks(): Promise<any>;
package/index.js CHANGED
@@ -17,7 +17,7 @@
17
17
  else root.MarginPadSDK = factory();
18
18
  })(typeof self !== 'undefined' ? self : this, function () {
19
19
  'use strict';
20
- var VERSION = '2.9.1';
20
+ var VERSION = '3.0.0';
21
21
 
22
22
  function MarginPadError(code, message, status, extra) {
23
23
  var e = new Error(code + ': ' + (message || code));
@@ -114,6 +114,22 @@
114
114
  // Pass the same fields to open() to override the account for one fill only.
115
115
  MarginPad.prototype.realism = function (o) { return o === undefined ? this._get('/api/bot/v2/realism') : this._post('/api/bot/v2/realism', o); };
116
116
 
117
+ // ── the whole simulation (3.0) ───────────────────────────────────────────
118
+ // realism() above is three switches of this; sim() is all eighteen fields. The defaults are deliberately
119
+ // easy - a market order fills whole at the live price, maintenance margin is a flat 0.5%, the balance never
120
+ // refuses an open, there is no latency and no size impact - which is right for getting a bot RUNNING and
121
+ // optimistic for judging its edge.
122
+ // await c.sim() -> your profile, every field with its range, the presets
123
+ // await c.sim({ preset: 'realistic' }) -> the honest middle, in one call
124
+ // await c.sim({ preset: 'bybit' }) -> Bybit's published fee and maintenance margin
125
+ // await c.sim({ preset: 'brutal' }) -> worse than anything you will meet; if it survives this, it survives
126
+ // await c.sim({ start_balance_usd: 2500, margin_enforced: true, slippage_bps: 8, latency_ms: 250 })
127
+ // Out-of-range values are REFUSED, never clamped, so a strategy is never measured under a setting you did
128
+ // not choose. A position keeps the simulation it was filled under, for life.
129
+ MarginPad.prototype.sim = function (o) { return o === undefined ? this._get('/api/bot/v2/sim') : this._post('/api/bot/v2/sim', o); };
130
+ // The eight presets, so an editor can complete them and a script can loop over them.
131
+ MarginPad.SIM_PRESETS = ['frictionless', 'realistic', 'binance', 'bybit', 'okx', 'hyperliquid', 'thin_book', 'brutal'];
132
+
117
133
  // ── webhooks (API Pro and up) ───────────────────────────────────────────────────────────────────────────────
118
134
  MarginPad.prototype.webhooks = function () { return this._get('/api/bot/v2/webhooks'); };
119
135
  MarginPad.prototype.addWebhook = function (url, events) { return this._post('/api/bot/v2/webhooks', { act: 'add', url: url, events: events }); };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "marginpad",
3
- "version": "2.9.1",
3
+ "version": "3.0.0",
4
4
  "description": "MarginPad Bot API client: free crypto-futures paper trading for bots (REST + WebSocket + webhooks) and free market data. Zero dependencies.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "license": "MIT",
8
- "repository": { "type": "git", "url": "https://github.com/marginpad/sdk" },
8
+ "repository": { "type": "git", "url": "git+https://github.com/Marginpad/sdk.git" },
9
9
  "homepage": "https://marginpad.io/trading-api/",
10
- "bugs": { "url": "https://github.com/marginpad/sdk/issues" },
10
+ "bugs": { "url": "https://github.com/Marginpad/sdk/issues" },
11
11
  "keywords": ["crypto", "futures", "paper-trading", "trading-bot", "backtest", "liquidations", "bybit", "binance", "api", "mcp", "ai-agent"],
12
12
  "engines": { "node": ">=18" },
13
13
  "files": ["index.js", "index.d.ts", "README.md"]