auspicium
Python SDK for the Auspicium market data platform — crypto OHLCV, order books, trades, Polymarket prediction markets, and cross-market signals.
Install
pip install auspicium
Quick start
import os
os.environ["AUSP_API_KEY"] = "your-api-key"
os.environ["AUSP_GATEWAY_URL"] = "https://api.auspicium.io"
from auspicium import rest
# OHLCV candlestick data
df = rest.ohlcv("binance", "BTC-USDT", interval="5m", days=7)
# Order book snapshot
book = rest.orderbook("binance", "BTC-USDT", depth=20)
# Polymarket prediction markets
markets = rest.markets(status="active", base_asset="BTC")
# Cross-market signal (Binance x Polymarket)
cross = rest.cross_market(granularity="5m", base_asset="BTC", hours=48)
WebSocket streaming
from auspicium import stream
async with stream.connect() as ws:
await ws.subscribe("ohlcv", source="binance", symbol="BTC-USDT")
async for msg in ws:
print(msg.channel, msg.payload)
Get your API key
Sign up at auspicium.io — free tier included.
Pointing the SDK at another environment
The SDK talks to whatever gateway AUSP_GATEWAY_URL points at (default
https://api.auspicium.io). To exercise it against the Quality DaaS:
export AUSP_GATEWAY_URL=https://api.quality.auspicium.io
export AUSP_API_KEY=<your quality API key>
No localhost DaaS is ever required. The unit test suite is fully mocked;
only tests/test_rest.py and tests/test_stream.py are integration tests
that need a live gateway (set the two variables above to run them).
Changelog
See CHANGELOG.md for the full per-version history. The most recent entries (newest first):
| Version | Date | Headline |
|---|---|---|
| 0.11.1 | 2026-08-31 | The copy-trader example submitted FOK orders on both sides; a killed exit strands the bot in the position (AUS-266). examples/copy_trader/strategy.py omitted order_type on both Order.binary_buy (entry) and Order.binary_sell (exit), inheriting the factory default "MARKET" — which on Polymarket is fill-or-kill: an order larger than the resting book is KILLED outright rather than filling the depth that exists (the AUS-194 trap, bundled CLAUDE.md §4). Both call sites now pass order_type="MARKET_FAK" explicitly, matching auspicium-studio's ported template, whose save-time linter has required it since AUS-194. The asymmetry is the point: a killed entry is a missed opportunity and the next signal comes along, but a killed exit leaves this bot holding a position it intended to leave — no retry, nothing in the log that reads as a problem, and the whale is already out — so it then rides to resolution, an outcome the user did not choose. Prediction-market books are thinnest exactly when a whale is taking profit, which is exactly when the exit fires. No book-depth preflight was added: book_depth_at_or_better returns 0.0 whenever the adapter cannot introspect the book (BacktestPaperExchange, Binance), so the §4 min(size, depth) recipe would silently zero every exit in backtest — and on an exit the honest fallback is a partial fill, not a skip. Example-only change: no SDK API moved, and no state.db action. Strategy.close_position() is untouched and still hardcodes order_type="MARKET" with no way to override it — tracked separately; the copy-trader deliberately does not route through it. |
| 0.11.0 | 2026-08-29 | The copy-trader bought the wrong side of every NO-pole market, and its backtest reported zero unsettled positions while holding them (AUS-244). The address channel's price is the price of the outcome token actually traded, not the YES price — measured across the quality corpus, price × size_shares ≈ size_usd holds for 97.6% of 233,880 NO-pole rows and matches zero rows under the YES-side reading. Both shipped copy-trader templates passed it straight into Order.binary_buy(yes_price=…), which inverts for the NO side, so a real Down fill at 0.868690 booked an entry at 0.1313, sized 380.8 shares instead of 57.6, and committed $330.78 of capital for a $50 stake. The error grows with distance from 0.5 — worst on the highest-conviction trades — and RiskGuardian could not catch it: it computes notional from the order's own price (380.8 × 0.1313 = $50) and so saw exactly the configured stake. On the exit side it was a money-loss path rather than mis-accounting: binary_sell would have offered a NO position worth 0.8687 at 0.1313, a marketable sell far below book. NO-pole rows are ~half the corpus (347,506 of 727,053). notebook_helper carried the same conflation in set_price(), so every copy backtest to date also mispriced NO-pole fills. Second, settlement never reached this path: run_copy_strategy_backtest had no resolutions= parameter at all, never called _warn_on_unsettled_binaries, and called _compute_stats positionally — so it reported settlement_modelled=False with unsettled_binary_positions=0 while holding open binaries. That pair reads as "held no binaries, settlement irrelevant"; a missing warning is a gap, an affirmative zero is a false statement. Fixed: resolutions= is now accepted and settles through the same SettlementEngine as run(), the UserWarning fires, and the count is real. Third, SELL mirroring — SELL is ~5.7% of trades corpus-wide and 4–19% on a typical tracked wallet, and the template discarded every one, so a copy-trader mirrored entries and never exited. It now mirrors the fraction of his position he sold, applied to your own holding (never his share count), no-ops when you hold nothing, refuses to gate exits on the entry dust filter, collapses to a full close near 100%, and logs loudly when a restart leaves the denominator unknown. copy_sells defaults true: scaffolded strategies are materialized to disk at scaffold time and are never rewritten, so no existing strategy changes behaviour. Also: the template now uses auspicium.outcomes.pole_of instead of a private mapping dict — that fourth copy was case-sensitive and missed Over/Under/Higher/Lower, all present in the corpus (Over 350 rows, Under 44), and gained only Long/Short, which appear in none of the 92 distinct outcome values across 727,053 TRADE rows. Docs: CLAUDE.md gains §10 (the whole channel; it previously had one passing phrase), Strategy.subscribe documents address, and the copy-backtest examples name the sync symbol — the async one binds a coroutine in a notebook and fails on .tearsheet() with no backtest run. run_copy_strategy_backtest's "arrives in v0.7.16" promises, unshipped for nine minor versions, are deleted. No state.db action. |
| 0.10.1 | 2026-08-25 | Binary markets now settle (AUS-167, Block 1). A resolved Polymarket position leaves the books — closed at exactly 1.0 or 0.0, cash credited, exposure released, realized PnL booked. New Settlement type and settlements table, deliberately not a Fill (no order, no counterparty, no fee); new Strategy.on_settlement hook and /api/settlements. Only a decisive YES/NO settles: not_decisive (11 of 100 sampled closed markets answer 0.5/0.5), not_a_binary_market, market_not_found and venue_unreachable each leave the position untouched, one test apiece — settling on any of them would book a fabricated outcome against real money. Polled on its own asyncio task (every 300 s, ≤25 markets/cycle, 10 s per lookup, ask-then-apply so a settlement can never delay a tick or race a fill), tunable under a settlement: section of config.json. emergency_close_all() stops being the accidental settlement path: the runner settles before it, so a resolved position never gets unwound at a stale mark. state.db bumps to v6 — delete it on upgrade. Numbers move: realized_pnl, daily_pnl, cash, unrealized_pnl, total_value, exposure, closed_trades, winning_trades/losing_trades, win_rate, profit_factor, avg_win/avg_loss, best_trade/worst_trade, total_return, total_pnl, max_drawdown, annualized_return, sharpe_ratio, sortino_ratio, and the pnl_positive gate (unpassable by construction before, passable now). total_trades and commission_paid deliberately do not move. The trade log gains an event column (fill/settlement) and settlement rows carry side="SETTLE". Backtests do not settle unless you supply the truth via run(resolutions={cid: "YES"}) — the SDK will not infer a settlement from a terminal price, and DaaS's resolution column is derived from a Binance candle and is empty anyway (AUS-236) — so a replay holding unsettled binaries now warns loudly and reports stats['settlement_modelled'] = False. |
| 0.10.0 | 2026-08-24 | config.json is now the only configuration file the SDK reads — manifest.yaml and config.yaml are gone as config sources. manifest.yaml contributed exactly one consumed key (risk) and config.yaml contributed params config.json already beat on every collision; both are now unread. They are inert, not deprecated — no startup guard, no presence check, no filesystem probe, deliberately, and pinned by a test. Move the risk: block verbatim into config.json: same four keys, same semantics, same precedence (per-instance config > config.json risk: > RiskGuardian defaults). A workspace's SDK is fixed at creation, so a strategy reaches this release only via a new workspace + re-promote — nothing migrates in place. The AUS-204 unconfigured-risk WARN survives, now points at config.json, and names every limit left at a default instead of falling silent once any one is declared — so a bot whose limits still sit in an unread manifest.yaml is told it is on SDK defaults. Also: the risk: section now merges per key across both sources (a partial per-instance override used to discard the author's other limits wholesale), and the file-side section is validated like the overlay. auspicium.config._load_yaml (the SDK's own client settings) is a different function and is untouched. No trading behaviour change, no state.db action. |
| 0.9.21 | 2026-08-24 | The SDK can now ask a venue whether a prediction market has settled, and to which side (AUS-167, Block 0). New auspicium.resolution (the venue-agnostic answer) plus BaseExchange.get_resolution(market) — an optional adapter capability shaped exactly like get_order_book, overridden by PolymarketExchange against the public gamma-api and routed by ProductionExchange. Nothing consumes it yet and no reported number changes: this block ends at "the SDK can ask and get a trustworthy answer". Settlement itself — closing the position at 0 or 1, booking realized PnL, releasing exposure — is the next block, carries a state.db bump, and is deliberately absent here. Why the venue and not our own database: DaaS's polymarket.markets.resolution is 100% NULL across 10,247 rows, its only writer is an Airflow DAG that is not deployed, and even deployed it would derive the outcome from Binance candles rather than Polymarket settlement (AUS-236). Three states, never two: Resolution.YES / NO / UNKNOWN, and the answer is always a MarketResolution object carrying a reason — never None, never a raise, and bool(res) raises TypeError on purpose so if res: cannot silently read an unknown as a resolution (this platform has six filed defects where absence and failure shared a representation). Ported from studio's production polymarket_market.py: the endpoint, the two-probe active-then-closed=true filter (gamma's default filter returns zero rows for a resolved market, verified live), the JSON-stringified outcomes / outcomePrices decoding, and the 24h/60s TTL. Changed for a long-running bot: the cache is per-adapter, not a module global, and bounded (studio's grows without limit, as does its parallel dict of asyncio.Locks); a transport failure is never negative-cached, because it is a fact about the moment, not about the market, and a 5-minute blind window after a blip would delay a settlement we could otherwise see immediately; the long TTL is earned by a decisive answer rather than by closed=true; and the whole lookup is bounded by a 6 s budget across both probes (connect 2 s, per-request 3 s) instead of an unbounded 2 x 5 s, because a bot must never die because gamma-api was slow. A closed market is not automatically a resolved one: 11 of 100 sampled closed markets carried outcomePrices of 0.5 / 0.5 — a tie, a void, or settlement in flight — and a three-outcome market is not expressible in complementary YES/NO at all; both answer UNKNOWN with a reason rather than crediting the first listed side. Backtest cannot reach the network by construction — BacktestPaperExchange holds no resolver and imports no venue client, and a full replay is asserted to issue zero HTTP requests. Unverified: whether a production bot container can egress to gamma-api.polymarket.com. Both bot networks are created internal=False and bots reach clob.polymarket.com, but a host firewall could block this specific name and that cannot be ruled out from code — so the first unreachable lookup WARNs once, naming the host and the egress question, and the answer is venue_unreachable, never market_open. No state.db action (schema stays v5). |
| 0.9.20 | 2026-08-24 | A live production bot was structurally unable to trade for eight hours and every channel that could have said so was silent (AUS-204). max_total_exposure_usd is measured over open positions, and binary settlement is unmodelled (AUS-167), so a hold-to-resolution position never leaves the books and exposure only rises: seven consecutive hourly refusals at an identical $200.85 would exceed $200.00 while the dashboard read Live / 100% win rate. Settlement is not modelled here (that is AUS-167) — the failure is made loud instead. Risk-class skips are pinned into the heartbeat regardless of rank (risk_blocked was buried under top_skips[:3]); three consecutive rejections of the same limit escalate to one WARN plus a new risk block on /api/status (escalated, rejections_by_limit, exposure_basis; risk_breaches was a hardcoded 0 and is now real) — no auto-kill, deliberately; a missing manifest.yaml or a manifest with no risk: section now WARNs naming the SDK defaults it silently put in force; and a correctly-spelled risk key at the top level of config.json / config.yaml — silently dropped, the third such path after AUS-128/AUS-136 — now warns. RiskGuardian publishes EXPOSURE_BASIS and says in its docstring and rejection message that on hold-to-resolution binaries the limit is a lifetime notional budget, not a concurrent-risk cap. No trading behaviour change, no state.db action. |
| 0.9.19 | 2026-08-23 | A routine upstream deploy killed every live production bot: the WS reconnect budget was ~41 seconds against a 7–20 minute deploy cycle, and exhausting it exited 0 (AUS-229). On 2026-08-22 a DaaS deploy closed the stream with code 1012 ("service restart"). Both live money-holding bots burned all ten reconnect attempts between 21:56:13 and 21:56:54, logged Max reconnect attempts (10) exceeded, and exited cleanly, status 0 — so RestartPolicy=unless-stopped restarted them and, with state.db then on tmpfs, destroyed their ledgers (AUS-228). DaaS was serving 200 OK again at 21:57:06, twelve seconds after the runner gave up. The retries were never an auth problem: 1012 / timeout / 503 / 502, never 401. The nominal budget was ~210s and the real one was 20s, and the gap is the actual defect: the healthy-connection reset ("don't penalise a bot that has been up for days after a blip") was re-evaluated on every attempt against last_connect_time, which is stamped only on a successful connect and was never cleared — so during an outage it still held the connect from hours earlier, the reset fired every time, and delay went back to 2.0s before every sleep. Exponential backoff was dead code for any bot up longer than 60 seconds: the longer a bot had run successfully, the faster it gave up. Measured from the loop, the shipped backoff was a flat [2.0] × 10 where the constants promise 2, 3, 4.5, … 60. Three changes: the termination condition is now a duration, not an attempt count (_reconnect_max_window_s, 30 min) — ten attempts is not a knowable amount of time, and the requirement being met ("survive a planned 7–20 min upstream deploy") has to be audited in the unit it is stated in; close code 1012 is recognised as planned maintenance and gets _reconnect_maintenance_window_s (60 min), other close codes deliberately do not; and the healthy-connection judgement now happens once per outage, after which last_connect_time is dropped so no later attempt can be judged on it — a bot up for days still gets its gentle first retry, then escalates normally. Terminating on connection loss now exits 75 (EX_TEMPFAIL), and an unhandled loop error exits 70 (EX_SOFTWARE); a fatal condition reporting success is why nothing anywhere detected this — no alert, no event, no ❌, the operator's only signal was the dashboard going blank. The tick-freshness watchdog and the AUS-93 tier-cap retry loop are unchanged (that loop is still bounded by _reconnect_max_attempts, which remains the right unit there). No trading behaviour changes and no state.db action (schema stays v5). |
| 0.9.18 | 2026-08-19 | GTD expiry validation ran against WALL-CLOCK, so a LIMIT_GTD order could not be constructed in a backtest at all (AUS-222). Order has no clock, so the model validator could only reach datetime.now(). In replay self.now() is the historical tick, so the honest idiom expires_at = max(candle_close, self.now() + 90s) is correctly ~90 s ahead in the replay frame and days in the past by wall-clock — rejected before the engine saw it. Measured: a 7-day GTD replay produced 1553 ValidationErrors and 0 trades. The only workaround was if self._backtest_mode: branching in strategy code — the exact thing Strategy.now() exists to abolish, and it means the backtest stops exercising the order type being shipped. The clock-relative checks (lead, future, 30-day cap) moved to validate_gtd_expiry(order, now), called at the submit boundary: Strategy.submit passes self.now(), PolymarketExchange.submit passes wall-clock. Structural checks (required on GTD, forbidden elsewhere, tz-aware) stay at construction. This is also the correction to 0.9.17, whose notes claimed its lead-time guard 'raises in backtest' — against wall-clock it was unreachable there. Also: binary_buy/binary_sell now accept expires_at, so building a GTD order no longer requires the raw Order(...) that golden rule #2 forbids. No state.db action. |
| 0.9.17 | 2026-08-19 | The SDK's GTD validation was weaker than the venue's, so a bad expiry failed ONLY in production (AUS-221). Order._validate_expires_at required just expires_at > now; Polymarket applies a security threshold and answers 400 invalid expiration value, must be in the future for GTD orders for a value that is future by the clock but too near. A strategy passed every local check, passed backtest — where the paper exchange never calls the venue — and failed only live, against real money. Measured on a live bot: expires_at = candle_close with an entry window admitting the final seconds of the hour, rejecting every order at ~15 POSTs/second. Order now requires a minimum lead time (AUSP_GTD_MIN_LEAD_S, default 60 s, 0 disables) so the failure lands in backtest instead. The exact venue threshold is NOT verified — 60 s is Polymarket's documented behaviour and matches the observed rejections, but no probe was executed, so the guard is tunable in both directions and does not claim to know the venue's rule. Also: the expiry is now logged — on submit and in the rejection message, with how far out it was. The venue's error does not echo the value it rejected and neither did we, so a bot could reject every order and leave no record of the number responsible; diagnosing it took reading the strategy's source. And an empty filtered get_trades now retries unfiltered with a warning — 0.9.15's market filter treated only exceptions as failure, so an empty result fell through silently and priced the fill from the quoted limit instead of the execution. No state.db action. |
| 0.9.16 | 2026-08-19 | An order the poll loop could not resolve was re-polled forever, silently (AUS-217). check_pending_orders removed an order from _open_orders on MATCHED/CONFIRMED or CANCELLED/CANCELED/EXPIRED/FAILED — and anything else fell through with no log line at all. Measured on a live bot: 15 orders submitted, 6 filled, 9 polled at 200 OK for up to 13 hours, with CANCELLED/EXPIRED appearing zero times in its entire log. Each held an _open_orders slot against max_open_orders (SDK default 20 — a bot bricks itself in under a day) and was re-requested every tick, which a degraded venue then amplified into a platform outage. The silence was the worst part: nobody could learn WHICH status to handle, because the loop never named the one it saw. It now reports any unresolved status — once per status change, not per poll — with the order's age and the open-order count. Retiring such an order is opt-in and OFF by default (AUSP_STALE_ORDER_MAX_AGE_S, seconds; 0 = disabled), because a confident default would be worse than the leak: a GTC LIMIT has no expiry and a LIMIT_GTD may legitimately rest for weeks, so any age the SDK picked would cancel orders somebody meant to place. When enabled it cancels first and only forgets the order if the venue confirms — an order we failed to cancel may still fill, and forgetting it would hide that fill from the portfolio. No state.db action. |
| 0.9.15 | 2026-08-19 | A TAKER fill recorded the COUNTERPARTY's price (AUS-215). _order_execution_from_trades priced our fill from maker_orders[].price, on the stated assumption that the per-maker legs are "true per-level prices". That holds when the maker sold the SAME token we bought; on a binary market's mint path the maker fills the other side by buying the complementary token, so their price is 1 - ours and we recorded the complement. Measured on a live production bot: a fill that executed at 0.87 was recorded at 0.13, and the cost basis read $5.78 for a fill that cost $39.01. Confirmed three ways — the Polygon receipt (OrderFilled for our own order hash: makerAmountFilled 38.6628 / takerAmountFilled 44.44 = 0.87), Polymarket's public Data API (0.870000), and the operator's activity feed. The bot's logs also settle intent: it submitted price=0.9000 on all 14 orders, so this was never an execution problem — the trade it asked for is the trade it got, mis-recorded. Our execution is now the trade's own top-level price; the maker legs are read only to CORROBORATE it (VWAP ≈ price → same-token; VWAP ≈ 1 − price → complementary; neither → claim nothing and let the caller fall back with a label). The maker branch, which selects our own leg by order id, was always correct and is untouched — every maker-branch fill in the incident recorded the right price and every taker-branch one recorded the complement. Also: get_trades is now filtered to the market (TradeParams supports it and the SDK passed nothing, scanning page one of the account's entire history — a bot's own recent trades eventually fall off it and the lookup silently returns nothing), and new Fill.price_source records WHICH rung of the price ladder produced price, so a consumer can finally tell a measured execution (response_amounts / venue_trade_log) from the price we merely asked for (order_status_limit / requested). None on pre-0.9.15 fills. No state.db action. |
| 0.9.14 | 2026-08-18 | Fix — a LIMIT/GTD fill lost the strategy's order id; new Fill.order_type. state.db stays v5, do NOT delete it. The order-status polling path — which serves every LIMIT/LIMIT_GTD order and every plain MARKET (FOK) whose POST response carried no two-sided amounts — recorded the venue's 0x… hash as Fill.order_id, replacing the strategy's <reason>:<uuid> tag. That broke three things at once: the dashboard's REASON and exit-path badges (parsed off the tag) went blank; StrategyRunner._on_fill frees the open-order slot by fill.order_id on a dict keyed by order.id, so the pop missed and after max_open_orders leaked fills the bot bricked itself at the risk check; and cancel() was unreachable, since Strategy.submit discards the exchange id and the venue does not know order.id. The fill keeps the strategy's id; the venue id moves to the new Fill.exchange_order_id; cancel() takes either. New Fill.order_type records what produced the fill — stamped on every Polymarket path plus paper and backtest, so a trades table reads identically in a notebook and in a live bot. None means "not recorded", never "MARKET" (guessing from the 0x shape is the AUS-192/AUS-198 defect — that shape cannot tell a maker GTD hash from a FOK CLOB orderID). The two columns are added in place by an additive migration, so no bot loses its portfolio state for a display field |
| 0.9.13 | 2026-08-14 | Fix — live plain-MARKET (FOK) Polymarket fills recorded fiction; state.db → v5, delete it on upgrade. Five real-money fills, five-for-five on-chain confirmation (2026-08-14): the SDK recorded Fill.size = actual_shares ÷ order_price (11.235954 executed → 12.2797 recorded), Fill.price = the tick-rounded quote (0.91 recorded, 0.89 executed) and Fill.fee = 0.0 (chain collected $0.0770). The 0.9.0 "legacy converter" assumed the polling response's size_matched was USDC on BUY; it is shares on both sides, on every shape that carries it — no published py-clob-client-v2 ever produced a USDC one, so the division is deleted. Executed price now comes from the response's two-sided amounts (a FOK that matches at submit is recorded there, like FAK) or the venue's trade log, with labelled fallbacks; fees are venue-reported or computed role-aware from the market's own fd schedule via auspicium.fees (the FAK path's hardcoded fee=0.0 closed too). Recorded live numbers move — to the truth; backtest/paper unchanged |
| 0.9.12 | 2026-08-11 | Fix — live orders on Up/Down Polymarket markets were impossible; no state.db action. The Polymarket adapter matched an order's canonical outcome ("YES"/"NO" — all Order.binary_buy accepts) against the venue's token label by exact string equality, and Polymarket's hourly Bitcoin markets label their tokens Up/Down. Every live entry on one raised OrderRejectedError: outcome 'YES' not found — a production bot logged 260 raises and 0 orders. Exact match still wins; when it misses, the lookup now falls back to the pole vocabulary (YES ↔ Up/Over/Above/Higher, NO ↔ Down/Under/Below/Lower), refuses to guess when two tokens share a pole, and lists the market's actual labels when nothing matches. Same fix reaches get_order_book / book_depth_at_or_better. New auspicium.outcomes module holds that vocabulary once (three copies before); pole_of("Up") == "YES" converts a wire label in strategy code |
| 0.9.11 | 2026-08-09 | Fix — win_rate counted fills, not round trips; the reported rate was about half the real one. No state.db action (stays v4). Backtest and live /api/status both divided winning rows by every row of _compute_trade_pnl, a table with one row per fill; opening BUYs carry pnl exactly 0.0, so the reported rate could not exceed 50% and a true 146/165 = 88.5% strike rate printed as 44.2%. Now wins over round trips, with the counts published: new closed_trades / winning_trades / losing_trades stats keys and a new closed_size column on the trades table (shares an exit matched against open lots). total_trades still counts fills. No other number moves — total_return was correct all along |
| 0.9.10 | 2026-08-05 | Fix — realized-PnL accounting; reported numbers move; state.db → v4, delete it on upgrade. The realized_pnl … diverges from FIFO gross WARNING was a false positive (it reconstructed gross as net + every fee, so drift equalled the fee total); it now replays the persisted fills through the same apply_fill the runner uses. _compute_trade_pnl now sorts fills chronologically (the live feed is newest-first), keys queues by (market, outcome), charges each closing trade its entry fee as well as its exit fee, and no longer mutates the caller's fills; new gross_pnl column. The runner deletes closed positions from state.db and rebuilds cash / realized PnL / positions from the fill log on start |
| 0.9.9 | 2026-08-01 | Behaviour change. New auspicium.fees — one role-aware, schedule-driven fee model shared by backtest and paper (market schedule → published category table → highest published rate; takers pay, makers pay 0 + rebate). Simulated Polymarket fees rise from a flat 0.02 to the resolved rate (crypto 0.07, 3.5×); the new number is the correct one. Binance unchanged |
| 0.9.8 | 2026-07-30 | Strategy.stake_amount() — launch-time position sizing from the per-instance config (stake_mode fixed / pct_cash / pct_equity, stake_pct, floor, cap); returns 0.0 below the floor and clamps to the risk limit max_position_usd instead of failing at submit |
| 0.9.7 | 2026-07-29 | Per-instance risk-limit overrides: the platform-delivered instance config's risk section merges per key over manifest.yaml's (instance config > manifest > RiskGuardian defaults); bad overrides warn and fall back instead of blocking a start. Changelog/README overhaul + drift guard |
| 0.9.6 | 2026-07-19 | Fix (live-bot reliability): symbol-less subscribe() on a symbol-keyed channel fails loudly at startup (was a silent 0-tick no-op); tick-freshness watchdog scales to the slowest ohlcv interval; runtime TIER_LIMIT on re-subscribe is recoverable, not fatal; disconnect reasons logged via repr() |
| 0.9.5 | 2026-07-16 | Fix: WebSocket URL now derives from AUSP_GATEWAY_URL (same host, wss, /v1/ws) instead of a hardcoded apex default; explicit AUSP_WS_URL still overrides |
| 0.9.4 | 2026-06-28 | RestConnector.address_trades accepts a period preset ("7d"/"30d"/"all"), mirroring address_stats |
| 0.9.3 | 2026-06-17 | Packaging: bundle auspicium/CLAUDE.md + auspicium/CHANGELOG.md inside the wheel — no runtime/API change |
| 0.9.2 | 2026-06-09 | Billing: account.me(), billing.create_checkout/create_portal, DaasRateLimitError; HTTP 429 is surfaced immediately (no auto-retry) |
| 0.9.1 | 2026-05-22 | Tests-only patch; runtime code identical to 0.9.0 (recommended pin over 0.9.0 for the cleaner sdist) |
| 0.9.0 | 2026-05-22 | BREAKING: Fill.size for Polymarket BUYs is now shares (was USDC). New Fill.notional_usd. state.db must be deleted on upgrade. |
| 0.8.0 | 2026-05-19 | Fix: ProductionExchange.get_order_book now delegates to the polymarket adapter (was returning None) |
| 0.7.20 | 2026-05-19 | MARKET_FAK / LIMIT_GTD order types + get_order_book + Strategy.book_depth_at_or_better |
| 0.7.19 | 2026-05-18 | Fix: Strategy.submit() pops _open_orders on exchange.submit() failure |
| 0.7.18 | 2026-05-17 | Polymarket WS payload carries market metadata (candle_open/close, active, …). BinaryMarketAggregator. Heartbeat silent-reject WARN. |
| 0.7.17 | 2026-05-16 | Fix: BacktestPaperExchange._positions re-keyed by (market, outcome) — YES + NO no longer net |
| 0.7.16 | 2026-05-15 | RestConnector.address_stats accepts arbitrary windows via from_ts / to_ts |
| 0.7.15 | 2026-05-15 | Copy-trading SDK (address_trades, address_stats, tracked_addresses, address WS channel, notebook helper) |
Releasing
Releases are cut from main and published to public PyPI by Cloud Build
(project auspicium). CI (cloudbuild.ci.yaml) runs
ruff + the unit suite on Python 3.11 and 3.12 for every push to dev and
every PR; the release pipeline (cloudbuild.release.yaml)
runs only on v* tags and publishes via PyPI Trusted Publishing (OIDC) — no
PyPI credential is stored anywhere.
To ship a change as, e.g., 0.9.5:
-
Develop on
dev. -
In the same change as the feature/fix, bump
auspicium/_version.py→"0.9.5"and add the entry to both changelogs — the repo-rootCHANGELOG.mdand the bundledauspicium/CHANGELOG.md(including its "Current pin" line). Keep the two in sync: the bundled copy is frozen into the wheel at tag time and is what a stack pinned to this version reads forever (same forauspicium/CLAUDE.md, the bundled operator guide — update it if behavior it describes changed). SemVer: patch = bugfix, minor = feature, major = breaking. -
Push
dev→ CI runs pytest + ruff. Nothing is published. -
Human merges
dev→main(PRs intomainrequire green CI). -
Human tags and pushes the tag — tags are human-only; agents never create or push tags:
git tag v0.9.5 git push origin v0.9.5
-
The release trigger then:
- guards: fails the build if the tag (
v0.9.5) ≠_version.py(0.9.5), so a mislabeled wheel can never ship; - re-runs the unit suite;
- builds
auspicium-0.9.5-py3-none-any.whl+.tar.gz; - uploads to PyPI with a short-lived OIDC-minted token.
- guards: fails the build if the tag (
-
pip install -U auspiciumnow resolves 0.9.5.
PyPI versions are immutable. A version number uploads exactly once and can never be re-uploaded — even after deleting it on PyPI. If 0.9.5 ships broken, fix it by releasing 0.9.6; never try to re-tag or re-upload 0.9.5 — a wrong tag burns the version number permanently. (This is also why the tag-vs-version guard exists: get the bump in before tagging.)
Versions are live runtimes (stacks)
Platform users run the SDK through stacks, each pinned to a released
version. The default stack runs whatever version is baked into the
Studio workspace image (auspicium/workspace:latest) — it does not
track PyPI automatically. Consequences:
- Publishing makes a version available, not live. Uploading
vX.Y.Zto PyPI is step one. It reaches default-stack users only after the studio repo bumps itsAUSPICIUM_SDK_VERSIONpin (.env) and rebuilds the workspace/bot images — a separate, gated flow (tracked as AUS-42). Users who want a published version sooner can create a custom stack pinned to it. - Old versions never die. Any released version remains a selectable stack runtime. Never yank or delete a PyPI release — existing stacks pin exact versions (and deletion wouldn't free the number anyway).
- The changelog is the version picker. Stack owners choose a pin from the At-a-glance table — headlines must be accurate, and breaking changes must be flagged explicitly.
History note — do NOT retro-tag ≤ 0.9.4
This repo currently has no git tags: releases 0.8.0 → 0.9.4 on PyPI were
published outside the tag-triggered pipeline, and the release guard has never
fired. Do not create tags v0.9.4 or earlier "to backfill history" —
each would trigger a doomed re-publish of an already-immutable version and a
red build. The next tagged release is the first live run of this
pipeline: watch it end-to-end.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file auspicium-0.11.1.tar.gz.
File metadata
- Download URL: auspicium-0.11.1.tar.gz
- Upload date:
- Size: 340.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.11.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8dfd8dc3fc03992e68542a1498f5144cacadcc5696cebdb7dc4230539084e38f
|
|
| MD5 |
e81df255769237073556c107625f1037
|
|
| BLAKE2b-256 |
a3b5d68292eea45199bc4ce57c5edd1af9b55acfccd17c6c8be3336c568024c3
|
File details
Details for the file auspicium-0.11.1-py3-none-any.whl.
File metadata
- Download URL: auspicium-0.11.1-py3-none-any.whl
- Upload date:
- Size: 315.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.11.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fbbe128dbc272faacc36462766c5f5df97e8f989c029a0bf9104af2dc66803c
|
|
| MD5 |
580c9d41dea284e3f9e419ded769202e
|
|
| BLAKE2b-256 |
c9ecc3dd47561a699fd077323f209e4d5f9014e722c178a15239a8a1ffa41cdd
|