glimpse-markets 0.1.0__tar.gz

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.
Files changed (33) hide show
  1. glimpse_markets-0.1.0/.github/workflows/ci.yml +32 -0
  2. glimpse_markets-0.1.0/.github/workflows/release.yml +70 -0
  3. glimpse_markets-0.1.0/.gitignore +27 -0
  4. glimpse_markets-0.1.0/CHANGELOG.md +52 -0
  5. glimpse_markets-0.1.0/LICENSE +21 -0
  6. glimpse_markets-0.1.0/PKG-INFO +334 -0
  7. glimpse_markets-0.1.0/README.md +295 -0
  8. glimpse_markets-0.1.0/examples/dry_run_strategy.py +68 -0
  9. glimpse_markets-0.1.0/pyproject.toml +90 -0
  10. glimpse_markets-0.1.0/src/glimpse_markets/__init__.py +63 -0
  11. glimpse_markets-0.1.0/src/glimpse_markets/_base.py +74 -0
  12. glimpse_markets-0.1.0/src/glimpse_markets/async_client.py +425 -0
  13. glimpse_markets-0.1.0/src/glimpse_markets/cli.py +228 -0
  14. glimpse_markets-0.1.0/src/glimpse_markets/client.py +401 -0
  15. glimpse_markets-0.1.0/src/glimpse_markets/enums.py +34 -0
  16. glimpse_markets-0.1.0/src/glimpse_markets/exceptions.py +97 -0
  17. glimpse_markets-0.1.0/src/glimpse_markets/models.py +392 -0
  18. glimpse_markets-0.1.0/src/glimpse_markets/money.py +32 -0
  19. glimpse_markets-0.1.0/src/glimpse_markets/py.typed +0 -0
  20. glimpse_markets-0.1.0/src/glimpse_markets/ratelimit.py +69 -0
  21. glimpse_markets-0.1.0/src/glimpse_markets/strategy.py +127 -0
  22. glimpse_markets-0.1.0/src/glimpse_markets/streaming.py +78 -0
  23. glimpse_markets-0.1.0/tests/test_async_client.py +304 -0
  24. glimpse_markets-0.1.0/tests/test_async_trade.py +277 -0
  25. glimpse_markets-0.1.0/tests/test_cli.py +263 -0
  26. glimpse_markets-0.1.0/tests/test_client.py +374 -0
  27. glimpse_markets-0.1.0/tests/test_exceptions.py +99 -0
  28. glimpse_markets-0.1.0/tests/test_money.py +21 -0
  29. glimpse_markets-0.1.0/tests/test_package.py +24 -0
  30. glimpse_markets-0.1.0/tests/test_ratelimit.py +94 -0
  31. glimpse_markets-0.1.0/tests/test_strategy.py +266 -0
  32. glimpse_markets-0.1.0/tests/test_streaming.py +177 -0
  33. glimpse_markets-0.1.0/tests/test_trade.py +426 -0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+
22
+ - name: Install package with dev dependencies
23
+ run: pip install -e ".[dev]"
24
+
25
+ - name: Lint (ruff)
26
+ run: ruff check .
27
+
28
+ - name: Type check (mypy)
29
+ run: mypy src
30
+
31
+ - name: Test (pytest)
32
+ run: pytest --cov -m "not integration"
@@ -0,0 +1,70 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ test:
10
+ name: Test
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - run: pip install -e ".[dev]"
20
+ - run: ruff check .
21
+ - run: mypy src
22
+ - run: pytest -m "not integration"
23
+
24
+ build:
25
+ name: Build distribution
26
+ needs: test
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - uses: actions/setup-python@v5
32
+ with:
33
+ python-version: "3.12"
34
+
35
+ - name: Verify tag matches package version
36
+ run: |
37
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
38
+ PKG_VERSION=$(python3 -c "import re; print(re.search(r'__version__ = \"(.+)\"', open('src/glimpse_markets/__init__.py').read()).group(1))")
39
+ echo "Tag version: $TAG_VERSION"
40
+ echo "Package version: $PKG_VERSION"
41
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
42
+ echo "::error::Git tag v$TAG_VERSION does not match __version__ ($PKG_VERSION) in src/glimpse_markets/__init__.py"
43
+ exit 1
44
+ fi
45
+
46
+ - run: pip install build twine
47
+ - run: python -m build
48
+ - run: twine check dist/*
49
+
50
+ - uses: actions/upload-artifact@v4
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ publish:
56
+ name: Publish to PyPI
57
+ needs: build
58
+ runs-on: ubuntu-latest
59
+ environment:
60
+ name: pypi
61
+ url: https://pypi.org/project/glimpse-markets/
62
+ permissions:
63
+ id-token: write # required for PyPI Trusted Publishing (OIDC) — no API token stored anywhere
64
+ steps:
65
+ - uses: actions/download-artifact@v4
66
+ with:
67
+ name: dist
68
+ path: dist/
69
+
70
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # Testing / coverage
12
+ .pytest_cache/
13
+ .coverage
14
+ .coverage.*
15
+ htmlcov/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+
19
+ # Env
20
+ .env
21
+ .env.*
22
+ !.env.example
23
+
24
+ # Editors / OS
25
+ .vscode/
26
+ .idea/
27
+ .DS_Store
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-09-17
11
+
12
+ Initial release.
13
+
14
+ ### Added
15
+
16
+ - `Client`, a synchronous client covering every endpoint of the Glimpse
17
+ Nmarket public API: wallet balance; batches; markets; live LMSR quotes;
18
+ market stats and volume; ended/resolved market listings; and portfolio
19
+ (active, ended-unresolved, ended-resolved, summary).
20
+ - Trade execution: `estimate_trade`, `enter_multi_topic_multi_leg`,
21
+ `exit_consolidated`, `exit_consolidated_multi`, `exit_multi_topic_multi_leg`,
22
+ `exit_batch`.
23
+ - `dry_run` mode on every trade-mutating call, pricing orders through the
24
+ public `/trades/estimate` endpoint instead of placing them. Glimpse has no
25
+ sandbox environment, so this is the client's own paper-trading safety net.
26
+ - `GlimpseAmbiguousTradeStateError`, raised instead of retrying on a network
27
+ failure mid-trade — the API has no idempotency key, so a blind retry could
28
+ double-execute an order.
29
+ - A `GlimpseError` exception hierarchy normalizing the API's inconsistent
30
+ error response shapes across endpoints.
31
+ - A client-side rate limiter matching Glimpse's 60-requests-per-60-seconds
32
+ per-key limit, so a naive bot loop doesn't immediately trip a 429.
33
+ - Pydantic v2 models for every response and request shape.
34
+ - `Millisats` / `PriceUnits` distinct types plus conversion helpers, to
35
+ prevent confusing the API's two numeric scales (real money vs. a 0-100
36
+ LMSR pricing scale).
37
+ - `AsyncClient`, mirroring `Client`'s entire method surface on
38
+ `httpx.AsyncClient`, for bots already running an asyncio event loop.
39
+ - `MarketStream`, a real-time client for Glimpse's public WebSocket feed
40
+ (`/ws/nmarket-updates`) — undocumented, found by reading the server
41
+ source and confirmed live.
42
+ - `Strategy` / `StrategyRunner` / `PositionTracker`, a minimal scaffold for
43
+ driving a trading bot off live quotes without hand-writing the
44
+ connect/dispatch/reconnect plumbing.
45
+ - The `glimpse` CLI: `balance`, `portfolio`, `batches`, `quotes`,
46
+ `estimate`, `execute`, `exit`, `exit-batch`, each supporting `--dry-run`
47
+ where applicable.
48
+ - `.env` / environment-variable configuration (`GLIMPSE_API_KEY`,
49
+ `GLIMPSE_BASE_URL`).
50
+
51
+ [Unreleased]: https://github.com/BIRKELAND-GLIMPSE/python-package/compare/v0.1.0...HEAD
52
+ [0.1.0]: https://github.com/BIRKELAND-GLIMPSE/python-package/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ritankar Saha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,334 @@
1
+ Metadata-Version: 2.5
2
+ Name: glimpse-markets
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the Glimpse Nmarket prediction-market API
5
+ Project-URL: Homepage, https://docs.glimpse.markets
6
+ Project-URL: Documentation, https://docs.glimpse.markets
7
+ Project-URL: Repository, https://github.com/BIRKELAND-GLIMPSE/python-package
8
+ Project-URL: Issues, https://github.com/BIRKELAND-GLIMPSE/python-package/issues
9
+ Author-email: Ritankar Saha <ritankar.saha786@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api-client,glimpse,lmsr,prediction-markets,trading,trading-bot
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business :: Financial :: Investment
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: click>=8.1
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: pydantic>=2.0
29
+ Requires-Dist: python-dotenv>=1.0
30
+ Requires-Dist: websockets>=13
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy>=1.11; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
34
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0; extra == 'dev'
36
+ Requires-Dist: respx>=0.21; extra == 'dev'
37
+ Requires-Dist: ruff>=0.6; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # glimpse-markets
41
+
42
+ The official Python client for [Glimpse](https://docs.glimpse.markets)'s
43
+ Nmarket prediction-market API — for people building forecasting algorithms
44
+ and trading bots on Glimpse without writing HTTP plumbing by hand.
45
+
46
+ ```bash
47
+ pip install glimpse-markets
48
+ ```
49
+
50
+ > **Status:** every read endpoint, every trade endpoint, the `glimpse` CLI,
51
+ > an async client, real-time streaming, and a small bot-building layer are
52
+ > all implemented.
53
+
54
+ ## Contents
55
+
56
+ - [Features](#features)
57
+ - [Quickstart](#quickstart)
58
+ - [Trading](#trading)
59
+ - [Paper trading (dry run)](#paper-trading-dry-run)
60
+ - [Async client](#async-client)
61
+ - [Real-time streaming](#real-time-streaming)
62
+ - [Building a bot](#building-a-bot)
63
+ - [CLI](#cli)
64
+ - [Units: millisats vs. price](#units-millisats-vs-price)
65
+ - [Error handling](#error-handling)
66
+ - [Development](#development)
67
+ - [License](#license)
68
+
69
+ ## Features
70
+
71
+ - **Sync and async clients** with an identical method surface — every
72
+ market-data, portfolio, and trading endpoint the public API exposes.
73
+ - **Typed responses.** Every call returns a [Pydantic](https://docs.pydantic.dev)
74
+ model, not a raw dict — autocomplete and validation instead of
75
+ `resp["message"]["..."]`.
76
+ - **Built-in paper trading.** Glimpse has no sandbox environment — every API
77
+ key is a live key. `dry_run=True` simulates trades client-side through the
78
+ free `/trades/estimate` endpoint, so you can test a strategy against real
79
+ live prices without risking real funds.
80
+ - **Real-time market data** over Glimpse's WebSocket feed, with no polling
81
+ loop to write yourself.
82
+ - **A minimal bot-building layer** (`Strategy` / `StrategyRunner`) for
83
+ wiring strategy logic up to the live feed without hand-rolling the
84
+ connect/dispatch/reconnect plumbing.
85
+ - **A `glimpse` CLI** for one-off calls from the terminal — check a
86
+ balance, get a quote, place a trade — without writing any code.
87
+ - **Safety around the sharp edges.** The API has no idempotency key, so a
88
+ network failure mid-trade is surfaced as a distinct
89
+ `GlimpseAmbiguousTradeStateError` instead of being silently retried (which
90
+ could double-execute a real trade) or silently swallowed.
91
+
92
+ ## Quickstart
93
+
94
+ ```python
95
+ from glimpse_markets import Client
96
+
97
+ with Client(api_key="glp_live_...") as client:
98
+ print(client.wallet_balance())
99
+
100
+ batch = client.batches().batches[0]
101
+ print(client.batch_active_markets(batch.batch_id))
102
+
103
+ # Market-data endpoints (batches, markets, quotes, stats) are public --
104
+ # no API key needed:
105
+ with Client() as client:
106
+ print(client.market_quotes(topic_id=6674))
107
+ ```
108
+
109
+ Or configure from the environment (`GLIMPSE_API_KEY`, `GLIMPSE_BASE_URL`),
110
+ optionally via a `.env` file in your working directory:
111
+
112
+ ```python
113
+ from glimpse_markets import Client
114
+
115
+ with Client.from_env() as client:
116
+ print(client.portfolio_summary())
117
+ ```
118
+
119
+ Generate an API key from your Glimpse account under **Settings → Developer
120
+ API Keys**.
121
+
122
+ ## Trading
123
+
124
+ ```python
125
+ from glimpse_markets import Client, TradeLeg, EnterMultiTopicLegGroup
126
+
127
+ with Client.from_env() as client:
128
+ # Always check cost and price impact first -- free, no API key required.
129
+ estimate = client.estimate_trade(6674, "buy", [TradeLeg(option_id=500, contracts=10)])
130
+ print(estimate)
131
+
132
+ topics = [EnterMultiTopicLegGroup(topic_id=6674, legs=[TradeLeg(option_id=500, contracts=10)])]
133
+ result = client.enter_multi_topic_multi_leg(topics)
134
+ print(result)
135
+
136
+ client.exit_consolidated(topic_id=6674, option_id=500) # exits the full position
137
+ ```
138
+
139
+ Entering a position is buy-only, by design of the underlying API — exit an
140
+ existing position to realize a "sell." `exit_consolidated_multi`,
141
+ `exit_multi_topic_multi_leg`, and `exit_batch` cover multi-leg and
142
+ whole-batch exits.
143
+
144
+ ## Paper trading (dry run)
145
+
146
+ Glimpse has no sandbox environment — every API key trades with real funds.
147
+ Pass `dry_run=True` (client-wide, or per call via `dry_run=...` on any trade
148
+ method) to paper-trade instead: every `enter`/`exit` call is priced through
149
+ the side-effect-free `/trades/estimate` endpoint rather than placing a real
150
+ order, and returns a `DryRunTradeResult` — a distinct type from a real
151
+ response, so a simulated fill can never be mistaken for a real one:
152
+
153
+ ```python
154
+ with Client.from_env(dry_run=True) as client:
155
+ result = client.enter_multi_topic_multi_leg(topics)
156
+ assert result.simulated is True # no order was placed
157
+ ```
158
+
159
+ A network failure while an `enter`/`exit` call is genuinely in flight
160
+ raises `GlimpseAmbiguousTradeStateError` rather than being retried — the API
161
+ has no idempotency key, so the client can't safely guess whether the trade
162
+ went through. Check `client.portfolio_active()` before resubmitting.
163
+
164
+ ## Async client
165
+
166
+ `AsyncClient` mirrors `Client`'s entire method surface — same names, same
167
+ signatures, `await` in front — built on `httpx.AsyncClient`, for bots
168
+ already running an asyncio event loop:
169
+
170
+ ```python
171
+ import asyncio
172
+ from glimpse_markets import AsyncClient, TradeLeg
173
+
174
+ async def main():
175
+ async with AsyncClient.from_env() as client:
176
+ print(await client.wallet_balance())
177
+ estimate = await client.estimate_trade(6674, "buy", [TradeLeg(option_id=500, contracts=10)])
178
+ print(estimate)
179
+
180
+ asyncio.run(main())
181
+ ```
182
+
183
+ ## Real-time streaming
184
+
185
+ Glimpse pushes live quote updates over a public WebSocket feed — no API key
186
+ required, no polling loop to write:
187
+
188
+ ```python
189
+ import asyncio
190
+ from glimpse_markets import AsyncClient
191
+
192
+ async def main():
193
+ async with AsyncClient() as client:
194
+ async with client.stream_market_updates(topic_id=6674) as stream:
195
+ async for update in stream:
196
+ print(update.data.quotes or update.data.binary_quotes)
197
+
198
+ asyncio.run(main())
199
+ ```
200
+
201
+ Pass `topic_id` and/or `batch_id` to filter the feed to one market or batch
202
+ (call `stream.subscribe(...)` again later to change the filter without
203
+ reconnecting); pass neither to receive every market's updates. There is
204
+ **no separate "market resolved" event on this feed** — detect resolution by
205
+ polling `quote_mode` via `market_quotes()` or `batch_active_markets_page()`
206
+ instead.
207
+
208
+ `MarketStream` also works standalone: `from glimpse_markets import MarketStream`.
209
+
210
+ ## Building a bot
211
+
212
+ `Strategy` and `StrategyRunner` wire a `MarketStream` up to your logic
213
+ without you writing the connect/dispatch/reconnect plumbing yourself:
214
+
215
+ ```python
216
+ import asyncio
217
+ from glimpse_markets import AsyncClient, MarketUpdate, Strategy, StrategyRunner
218
+
219
+ class MyStrategy(Strategy):
220
+ async def on_quote(self, update: MarketUpdate) -> None:
221
+ # called for every market_update from the stream
222
+ print(update.topic_id, update.data.quotes)
223
+
224
+ async def on_tick(self) -> None:
225
+ # called every `tick_interval` seconds, independent of quote events
226
+ positions = await self.positions.get() # cached portfolio_active()
227
+ print(f"{len(positions)} open positions")
228
+
229
+ async def main():
230
+ client = AsyncClient.from_env(dry_run=True) # paper-trade by default
231
+ runner = StrategyRunner(MyStrategy(), client=client, topic_id=6674, tick_interval=5.0)
232
+ await runner.run()
233
+
234
+ asyncio.run(main())
235
+ ```
236
+
237
+ `self.client` (the `AsyncClient`) and `self.positions` (a `PositionTracker`)
238
+ are available inside any hook — place trades with the former, check current
239
+ positions with the latter without re-fetching your whole portfolio on every
240
+ quote tick. `PositionTracker` caches `portfolio_active()` and refreshes at
241
+ most once every few seconds; call `self.positions.invalidate()` right after
242
+ placing a trade to force a fresh read.
243
+
244
+ An exception raised from `on_quote` or `on_tick` stops the runner and
245
+ propagates out of `run()` — a strategy bug fails loud instead of getting
246
+ silently swallowed. See [`examples/dry_run_strategy.py`](./examples/dry_run_strategy.py)
247
+ for a complete, runnable example.
248
+
249
+ ## CLI
250
+
251
+ The `glimpse` command covers the same ground as the Python client, for
252
+ one-off calls from the terminal. Config comes from `GLIMPSE_API_KEY` /
253
+ `GLIMPSE_BASE_URL`, read from the environment or a `.env` file in the
254
+ current directory.
255
+
256
+ ```bash
257
+ glimpse balance
258
+
259
+ glimpse portfolio active
260
+ glimpse portfolio summary
261
+ glimpse portfolio ended [--limit N --offset N]
262
+ glimpse portfolio resolved [--limit N --offset N]
263
+
264
+ glimpse batches # list all batches
265
+ glimpse batches --batch-id <id> # active markets in a batch
266
+ glimpse quotes --topic-id <id>
267
+
268
+ glimpse estimate --topic-id <id> --type buy --leg 500:10 [--leg 501:5]
269
+ glimpse execute --topic-id <id> --leg 500:10 [--dry-run]
270
+ glimpse exit --topic-id <id> --option-id 500 [--shares 5] [--dry-run]
271
+ glimpse exit-batch --batch-id <id> [--dry-run]
272
+ ```
273
+
274
+ `--dry-run` works the same way it does in the Python client. `execute` and
275
+ a partial `exit --shares N` never need an API key in dry-run mode, since
276
+ both are priced entirely through the public estimate endpoint. A
277
+ full-position `exit` (no `--shares`) or `exit-batch` still needs a key even
278
+ in dry-run mode, since pricing them requires looking up your current
279
+ positions first.
280
+
281
+ ## Units: millisats vs. price
282
+
283
+ The API mixes two numeric scales that are easy to confuse:
284
+
285
+ - **millisats** (`*_millisats` fields, wallet balance) — real money.
286
+ 1 satoshi = 1000 millisatoshis.
287
+ - **price** (`yes_price` / `no_price` / `odds` from quotes) — a 0–100
288
+ LS-LMSR pricing scale, only converted to millisats at trade/settlement
289
+ time.
290
+
291
+ `glimpse_markets.money` exposes `Millisats` and `PriceUnits` as distinct
292
+ types, plus `millisats_to_sats()` / `sats_to_millisats()` helpers, so it's
293
+ harder to accidentally treat a price of `62.1` as `62.1` millisats.
294
+
295
+ ## Error handling
296
+
297
+ Every non-2xx response raises a subclass of `GlimpseAPIError`
298
+ (`GlimpseAuthenticationError`, `GlimpseForbiddenError`,
299
+ `GlimpseTradingNotEligibleError`, `GlimpseNotFoundError`,
300
+ `GlimpseValidationError`, `GlimpseRateLimitError`, `GlimpseServerError`),
301
+ each carrying `.status_code`, `.error`, `.reason`, `.message`, and the raw
302
+ response body:
303
+
304
+ ```python
305
+ from glimpse_markets import GlimpseAPIError
306
+
307
+ try:
308
+ client.wallet_balance()
309
+ except GlimpseAPIError as e:
310
+ print(e.status_code, e.error, e.reason)
311
+ ```
312
+
313
+ Both clients also throttle themselves client-side to stay under Glimpse's
314
+ 60-requests-per-60-seconds-per-key limit, so a naive loop doesn't
315
+ immediately trip a 429.
316
+
317
+ ## Development
318
+
319
+ ```bash
320
+ git clone <repo-url>
321
+ cd python-package
322
+
323
+ python3 -m venv .venv
324
+ source .venv/bin/activate
325
+ pip install -e ".[dev]"
326
+
327
+ ruff check .
328
+ mypy src
329
+ pytest
330
+ ```
331
+
332
+ ## License
333
+
334
+ MIT — see [LICENSE](./LICENSE).