briskapi 0.2.0__py3-none-any.whl

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.
briskapi/timing.py ADDED
@@ -0,0 +1,72 @@
1
+ """Timing-only statistics for sessions whose market data can't be shared (SBI BRiSK)."""
2
+ from __future__ import annotations
3
+
4
+ import datetime as dt
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ from briskapi._recording import JST
8
+ from briskapi.schema import MIN_TIMING_FRAMES, QUANTILES, TIMING_BOUNDS, TIMING_SCHEMA, validate_timing
9
+
10
+ MAX_SAMPLES = 1_000_000
11
+ STALL_MS = 1000
12
+
13
+
14
+ def _client_version():
15
+ try:
16
+ return version('briskapi')
17
+ except PackageNotFoundError: # pragma: no cover - running from an uninstalled checkout
18
+ return '0.0.0'
19
+
20
+
21
+ def _distribution(values, low, high):
22
+ ordered = sorted(min(max(v, low), high) for v in values)
23
+ pick = lambda q: ordered[min(len(ordered) - 1, int(q * len(ordered)))]
24
+ return {name: round(value, 3) for name, value in zip(QUANTILES, (pick(.5), pick(.9), pick(.99), ordered[-1]))}
25
+
26
+
27
+ class TimingStats:
28
+ """Accumulates per-frame decode time, data age at receipt and frame spacing.
29
+
30
+ Only these local measurements are kept; prices, quantities and codes are not.
31
+ """
32
+
33
+ def __init__(self, source):
34
+ self.source = source
35
+ self.trading_date = None
36
+ self.frames = self.stalls = 0
37
+ self.first = self.last = None
38
+ self.decode, self.age, self.gaps = [], [], []
39
+ self._midnight_ms = None
40
+
41
+ def add(self, batch):
42
+ if batch['type'] == 'bootstrap':
43
+ self.trading_date = batch['trading_date']
44
+ day = dt.datetime.strptime(self.trading_date, '%Y%m%d').replace(tzinfo=JST)
45
+ self._midnight_ms = day.timestamp() * 1000
46
+ return
47
+ received = batch.get('received_unix_ms')
48
+ if batch['type'] != 'quotes' or received is None or self.frames >= MAX_SAMPLES:
49
+ return
50
+ self.frames += 1
51
+ if self.last is not None:
52
+ gap = received - self.last
53
+ self.gaps.append(gap)
54
+ self.stalls += gap > STALL_MS
55
+ self.first = self.first if self.first is not None else received
56
+ self.last = received
57
+ self.decode.append(batch.get('decode_ns', 0) / 1e6)
58
+ # Age of the feed's own clock at local receipt; only as accurate as both clocks.
59
+ self.age.append(received - (self._midnight_ms + batch['source_time_us'] / 1000))
60
+
61
+ def report(self, contributor, license):
62
+ """The shareable report, or None when the session was too short to say anything."""
63
+ if self.trading_date is None or self.frames < MIN_TIMING_FRAMES:
64
+ return None
65
+ minute = lambda ms: dt.datetime.fromtimestamp(ms / 1000, JST).strftime('%H:%M')
66
+ report = dict(schema=TIMING_SCHEMA, source=self.source, trading_date=self.trading_date,
67
+ first_minute=minute(self.first), last_minute=minute(self.last), frames=self.frames,
68
+ stalls=self.stalls, client_version=_client_version(), contributor=contributor, license=license,
69
+ decode_ms=_distribution(self.decode, *TIMING_BOUNDS['decode_ms']),
70
+ source_age_ms=_distribution(self.age, *TIMING_BOUNDS['source_age_ms']),
71
+ interarrival_ms=_distribution(self.gaps or [0], *TIMING_BOUNDS['interarrival_ms']))
72
+ return validate_timing(report)
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: briskapi
3
+ Version: 0.2.0
4
+ Summary: Unofficial Python API, live feed and shared archive for BRiSK auction data
5
+ License-Expression: MIT
6
+ Project-URL: Source, https://github.com/honvl/BRiSKapi
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: boto3<2,>=1.41
11
+ Provides-Extra: pandas
12
+ Requires-Dist: pandas>=2; extra == "pandas"
13
+ Dynamic: license-file
14
+
15
+ # briskapi
16
+
17
+ [English](README.md) | [日本語](README.ja.md)
18
+
19
+ An unofficial, pybrisk-style Python API and `brisk` command line for BRiSK auction
20
+ data. Consume a live feed, query recordings at any point in time, pull shared
21
+ recordings from a public archive, and use SBI BRiSK with your own account. This
22
+ is an independent project, not affiliated with or endorsed by BRiSK, Tachibana,
23
+ SBI, TSE or JPX.
24
+
25
+ > **Data sources:**
26
+ > - **No account needed:** the public BRiSK Next demo of 27 September 2021 (one
27
+ > pre-open snapshot and the first three minutes after the open), replayed at
28
+ > its recorded pace. This is not live market data.
29
+ > - **With an SBI Securities BRiSK subscription:** SBI BRiSK market data (candles,
30
+ > margin, alerts, schedule, watchlist) and an experimental live feed.
31
+
32
+ ## Install
33
+
34
+ Python 3.12+. Live feeds and recording also need Node 22+, because BRiSK's own
35
+ decoder is a WebAssembly module that runs under Node.
36
+
37
+ ```sh
38
+ pip install 'briskapi[pandas]' # `import briskapi` and the `brisk` command
39
+ ```
40
+
41
+ BRiSK's decoder and demo data are downloaded at runtime; the package doesn't
42
+ include them. Optional prebuilt Rust tools (`brisk_quote_ingest`,
43
+ `brisk_recording`) for Linux, macOS and Windows are attached to each
44
+ [GitHub release](https://github.com/honvl/BRiSKapi/releases). To work from source,
45
+ clone the repository and run `pip install -e '.[pandas]'`.
46
+
47
+ ## Live feed
48
+
49
+ ```python
50
+ import briskapi
51
+
52
+ feed = briskapi.connect(web=True, codes=["7203", "6758"]) # returns once initial state is in
53
+ toyota = briskapi.Ticker("7203")
54
+ toyota.quote() # current quote, updated as each frame arrives
55
+ toyota.auction() # indicative price/volume and market-order imbalance
56
+
57
+ feed.on_quote(lambda q: print(q["code"], q["indicative_price"]), codes="6758")
58
+ for q in feed.quotes("7203"): # one item per update; ends with the session
59
+ if q["last_price"]:
60
+ print("opened at", q["last_price"], q["time"])
61
+ break
62
+
63
+ briskapi.Market().imbalances(top=10).to_pandas()
64
+ feed.wait() # or feed.close(); `with briskapi.connect(...) as feed:` also works
65
+ ```
66
+
67
+ Options: `web=True` (fetch from the demo site) or `cache=DIR` (a local copy of
68
+ the demo assets), `codes`, `speed` (`1` real time, `0` as fast as possible) and
69
+ `history=True` (keep updates for `Ticker.history()`). Callbacks and iterators
70
+ first receive each security's current quote, then every update in order. A slow
71
+ consumer slows the feed instead of losing updates.
72
+
73
+ ## Recordings and the archive
74
+
75
+ ```python
76
+ briskapi.recordings(source="historical_mock") # published recordings; no AWS account needed
77
+ briskapi.pull("archive/20210927/SHA256") # download, verify, decode and cache; becomes the default
78
+ briskapi.load("recordings/my-session") # or a local recording (events.jsonl[.gz] or folder)
79
+ briskapi.record("recordings/my-session", web=True) # record the demo yourself
80
+
81
+ briskapi.Ticker("7203").quote(at="08:59:59.99") # state at any JST time
82
+ briskapi.Ticker("7203").history(start="09:00", end="09:01") # every update in a window
83
+ briskapi.Market().snapshot(at="09:00:00").to_pandas()
84
+ ```
85
+
86
+ ## SBI BRiSK
87
+
88
+ For SBI Securities customers with a BRiSK subscription. Log in on
89
+ [sbi.brisk.jp](https://sbi.brisk.jp) in your browser, then pass its session
90
+ cookies: copy them from DevTools, or use `pycookiecheat`'s
91
+ `chrome_cookies("https://sbi.brisk.jp")`.
92
+
93
+ ```python
94
+ from briskapi import sbi
95
+
96
+ sbi.login(cookies={"session_bfaf77a2": "v2.local..."}) # remember=True saves them (owner-only file)
97
+ toyota = briskapi.Ticker("7203")
98
+ toyota.candles("5m").to_pandas() # price bars: 5m (today), 1d, 1w or 1mo
99
+ toyota.margin(days=30) # margin balances and stock-lending fees
100
+ market = briskapi.Market()
101
+ market.turnover() # turnover and shares outstanding, all stocks
102
+ market.lists() # NK225, recent IPOs, …
103
+ market.events() # basket orders, limit up/down, volume surges
104
+ market.schedule() # trading date, status and session times
105
+ market.watchlist() # your saved codes
106
+
107
+ feed = sbi.connect(codes=["7203"]) # live (experimental)
108
+ toyota.quote() # same calls as any feed
109
+ ```
110
+
111
+ Results use the conventions below. Errors are `sbi.SessionExpiredError` (log in
112
+ again), `briskapi.NotFoundError`, `sbi.RateLimitError` and `sbi.APIError`.
113
+ Requests are limited to one per second.
114
+
115
+ The live feed runs SBI's own decoder under Node, downloaded with your session;
116
+ no browser is involved. It hasn't yet been validated against a live SBI session,
117
+ so it fails with an explicit error rather than guessing. Please report what you
118
+ see. Your cookies go only to sbi.brisk.jp, and SBI market data never leaves
119
+ your computer. With sharing on, a session contributes only a timing summary (see
120
+ below).
121
+
122
+ ## API reference
123
+
124
+ | Call | Returns |
125
+ | --- | --- |
126
+ | `briskapi.connect(...)` | Live `Feed`; becomes the default source |
127
+ | `Ticker(code).info()` | Name, lot size, tick type, base price and daily limits |
128
+ | `Ticker(code).quote(at=None)` | Bid/ask, indicative price/volume, market-order and closing quantities, last trade |
129
+ | `Ticker(code).auction(at=None)` | Indicative auction state with `market_order_imbalance` (market buy minus sell) |
130
+ | `Ticker(code).history(start, end)` | Every update in order |
131
+ | `Market().stocks()` | Master for every security |
132
+ | `Market().snapshot(at=None)` | Every security's quote |
133
+ | `Market().imbalances(at=None, top=None)` | Securities ranked by absolute market-order imbalance |
134
+ | `Market().summary()` | Source, date, coverage and clock range |
135
+ | `Feed.quotes(codes)` / `Feed.on_quote(fn, codes)` | Live updates as they arrive |
136
+ | `briskapi.recordings()` / `.pull()` / `.load()` | Archive listing, verified download, local file |
137
+ | `briskapi.record(output, web=True, ...)` | A recording of the demo, shared per your choice |
138
+ | `briskapi.consent(...)` | Your sharing choice |
139
+ | `Ticker(code).candles(interval)` / `.margin(days)` | SBI BRiSK price bars; margin balances and lending fees |
140
+ | `Market().turnover()` / `.lists()` / `.events()` / `.schedule()` / `.watchlist()` | SBI BRiSK market data |
141
+ | `briskapi.sbi.login()` / `.connect()` | SBI BRiSK session and live feed |
142
+
143
+ Prices are yen floats, with `None` for the vendor's zero "unavailable" value.
144
+ Times are JST `datetime`s on the trading date. Quantities are shares; side, flag
145
+ and status codes are raw vendor values. `raw=True` returns vendor fields
146
+ (`*_price10` in tenths of a yen, `*_us` in microseconds since JST midnight).
147
+ Tabular results are lists of dicts with `.to_pandas()`. Errors are
148
+ `briskapi.BriskError` and `briskapi.NotFoundError`. A whole-market query reads a
149
+ recording once (about six seconds for the complete 420 MB demo).
150
+
151
+ ## Command line
152
+
153
+ ```sh
154
+ brisk live --web --codes 7203,6758 # one JSON object per quote update (--raw for vendor fields)
155
+ brisk live --sbi --codes 7203 # SBI BRiSK; cookies from BRISK_SBI_COOKIES (JSON)
156
+ brisk record --web --output recordings/s1 # record a replay (shared if you agreed)
157
+ brisk list --date 20210927 --source historical_mock
158
+ brisk pull archive/20210927/SHA256 --output recordings/downloaded
159
+ brisk consent [--accept | --revoke] # show or change sharing
160
+ brisk upload recordings/s1 # retry sharing a recording
161
+ ```
162
+
163
+ Each command has `--help`. `pull` verifies everything before writing and never
164
+ overwrites an existing folder.
165
+
166
+ ## Sharing recordings
167
+
168
+ The first time you record or start a demo live session from the command line,
169
+ the tool shows what would be shared and asks once; Enter accepts. After that,
170
+ every complete demo session is uploaded and published automatically. The Python
171
+ API never asks: until you decide, sessions stay on your computer.
172
+
173
+ - **SBI sessions share timing only:** percentiles of decode time, data age at
174
+ receipt and frame spacing, a stall count, the frame count, the trading date,
175
+ the first and last minute, and your alias and license. Never prices,
176
+ quantities or codes. `briskapi.Archive().timing()` lists everyone's reports.
177
+ - **What a demo session shares:** the market data you recorded, local timing measurements
178
+ (including your computer's clock, which shows when you recorded), and a public
179
+ alias (random `anon-…` by default) and license. Your IP address is used only to
180
+ rate limit uploads.
181
+ - **Visibility:** published recordings are public and permanent, and you cannot
182
+ delete them yourself. See [PRIVACY.md](PRIVACY.md).
183
+ - **Opting out:** `brisk consent --revoke`, `BRISK_CONTRIBUTE=0`, or `--no-upload`
184
+ for one run.
185
+ - **License:** accepting declares that you may redistribute the recordings under
186
+ the chosen data license (CC0-1.0 or CC-BY-4.0). This project's open-source
187
+ license gives no rights to vendor or exchange data. If you can't make that
188
+ declaration, turn sharing off.
189
+ - Partial replays (`--limit-frames`) and sessions closed early are never shared.
190
+
191
+ ## More documentation
192
+
193
+ - [ARCHITECTURE.md](ARCHITECTURE.md): how it works, data format, archive integrity and limits
194
+ - [PRIVACY.md](PRIVACY.md): privacy policy
195
+ - [CONTRIBUTING.md](CONTRIBUTING.md): development, tests and releases
196
+ - [tools/brisk_mock/README.md](tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
197
+ - [tools/brisk_mock/NAUTILUS_V2.md](tools/brisk_mock/NAUTILUS_V2.md): NautilusTrader v2 integration
198
+ - [infra/README.md](infra/README.md): deploying your own archive
199
+ - [THIRD_PARTY.md](THIRD_PARTY.md): decoder, data and pybrisk attribution
200
+
201
+ Software is MIT licensed.
@@ -0,0 +1,23 @@
1
+ briskapi/LICENSE-pybrisk.txt,sha256=GqZvm14wEnuBJvyaV_H_TaKkJ7KGw24HLTzjl140-KM,1067
2
+ briskapi/__init__.py,sha256=Ct1l9f6a87WXOvHudgehcsVSIB--2rq63ytE2Znp60A,2718
3
+ briskapi/__main__.py,sha256=s-7n5-jcsEeiHahAAEbAeAefMI4zNxPjzeAqmAGY0iI,38
4
+ briskapi/_archive.py,sha256=_BwrBmlKxIa8JvDBoZugirTl_b28jrru-3_YbTguiDk,2680
5
+ briskapi/_live.py,sha256=vinfopccNKO57O4PGdYWzwM8MO3kYR3-U0_TYxvBy_o,15976
6
+ briskapi/_market.py,sha256=u2GHHwVzIpFjJVkA5aqU_BpmyWxF06AAbT5F4Q_e1R0,5799
7
+ briskapi/_recording.py,sha256=Vzv1tzHos-_Xx9DZzTZzH-2FshslCpubxr_KTqiNT6Y,7412
8
+ briskapi/archive.json,sha256=C21yu75dxUc8u1-QH7jqlUWbkOQ2t-K7fleRSXrbTt8,168
9
+ briskapi/cli.py,sha256=8hf9rcKt6EKqRS2HJbD0eDG_mxNoIKZNGmoQwmFu6HU,18166
10
+ briskapi/sbi.py,sha256=zN_ZHz6Fcb_fDkDtf4N30FZDMNE3p3vzu2C6UXZfqXU,11860
11
+ briskapi/schema.py,sha256=tsyhhUt5qHmYm7RkOfk-OmOm_udFvzFlZ1U0EyxvDG8,19121
12
+ briskapi/timing.py,sha256=KcbYMs_t9C81RICYw_W9MSPp9NMcyjws2kb6UF9mRZA,3239
13
+ briskapi/decoder/assets.json,sha256=fdTbjIUQs_Cypz5mbxqA0y-Ek6EkAPf7mTsvTG_e4i4,1090
14
+ briskapi/decoder/decoder.cjs,sha256=RS9Ygf-vHD4thGD-FiMuriM8iAZcUERco04HBd2J95U,13783
15
+ briskapi/decoder/sbi.cjs,sha256=7RwexPlCQML2uG1_5tjMEXdn5eo1qlve0llZ3WPoBRk,8145
16
+ briskapi/decoder/web.cjs,sha256=d91IZIuo_nB4dIlLYO3_iKXotUR41OPbUFGaWnlZS6w,1815
17
+ briskapi/references/historical_mock.json,sha256=geCqUFVhOh97eUNcIPs094A5L-pDj4R343ZfezR1x8w,157185
18
+ briskapi-0.2.0.dist-info/licenses/LICENSE,sha256=NUMKVN8YDNTxtVQykfNwr_bjYoa0YhIPlsXtw_Ka11g,1062
19
+ briskapi-0.2.0.dist-info/METADATA,sha256=KDVQ04JKpqypYWa9A-srT4tj4xnR0gI5-6u5i4XYv5Q,9891
20
+ briskapi-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
+ briskapi-0.2.0.dist-info/entry_points.txt,sha256=wylW3PTuLnIZJzCjgacu4i_SuEB692jv_XTXNITIFLQ,44
22
+ briskapi-0.2.0.dist-info/top_level.txt,sha256=xw3G1yaZBJxHavgu2bMbd3Y-nxRnlmIBcH5yT4BdskA,9
23
+ briskapi-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ brisk = briskapi.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 honvl
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 @@
1
+ briskapi