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.
- glimpse_markets-0.1.0/.github/workflows/ci.yml +32 -0
- glimpse_markets-0.1.0/.github/workflows/release.yml +70 -0
- glimpse_markets-0.1.0/.gitignore +27 -0
- glimpse_markets-0.1.0/CHANGELOG.md +52 -0
- glimpse_markets-0.1.0/LICENSE +21 -0
- glimpse_markets-0.1.0/PKG-INFO +334 -0
- glimpse_markets-0.1.0/README.md +295 -0
- glimpse_markets-0.1.0/examples/dry_run_strategy.py +68 -0
- glimpse_markets-0.1.0/pyproject.toml +90 -0
- glimpse_markets-0.1.0/src/glimpse_markets/__init__.py +63 -0
- glimpse_markets-0.1.0/src/glimpse_markets/_base.py +74 -0
- glimpse_markets-0.1.0/src/glimpse_markets/async_client.py +425 -0
- glimpse_markets-0.1.0/src/glimpse_markets/cli.py +228 -0
- glimpse_markets-0.1.0/src/glimpse_markets/client.py +401 -0
- glimpse_markets-0.1.0/src/glimpse_markets/enums.py +34 -0
- glimpse_markets-0.1.0/src/glimpse_markets/exceptions.py +97 -0
- glimpse_markets-0.1.0/src/glimpse_markets/models.py +392 -0
- glimpse_markets-0.1.0/src/glimpse_markets/money.py +32 -0
- glimpse_markets-0.1.0/src/glimpse_markets/py.typed +0 -0
- glimpse_markets-0.1.0/src/glimpse_markets/ratelimit.py +69 -0
- glimpse_markets-0.1.0/src/glimpse_markets/strategy.py +127 -0
- glimpse_markets-0.1.0/src/glimpse_markets/streaming.py +78 -0
- glimpse_markets-0.1.0/tests/test_async_client.py +304 -0
- glimpse_markets-0.1.0/tests/test_async_trade.py +277 -0
- glimpse_markets-0.1.0/tests/test_cli.py +263 -0
- glimpse_markets-0.1.0/tests/test_client.py +374 -0
- glimpse_markets-0.1.0/tests/test_exceptions.py +99 -0
- glimpse_markets-0.1.0/tests/test_money.py +21 -0
- glimpse_markets-0.1.0/tests/test_package.py +24 -0
- glimpse_markets-0.1.0/tests/test_ratelimit.py +94 -0
- glimpse_markets-0.1.0/tests/test_strategy.py +266 -0
- glimpse_markets-0.1.0/tests/test_streaming.py +177 -0
- 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).
|