Synthetic after-hours quote generator
Project description
afterquote
Synthetic after-hours pricing for leveraged and cross-currency securities.
The London market closes at 4:30pm. TSLA keeps trading until 9pm EST. If you hold 3TSL.L — a 3× leveraged ETP in GBp — you have no live price for the next several hours. afterquote fills that gap: it synthesises a real-time OHLC quote by applying the underlying's move (with leverage and FX adjustment) to the last known close.
pip install afterquote
from afterquote import SecurityPair
pair = SecurityPair("3TSL.L", "TSLA")
pair.info()
base_security underlying_security base_is_live leverage base_close_time base_close_price adj_percent_return quote_price
quote_time
2026-06-19 00:59:00+01:00 3TSL.L TSLA False 3 2026-06-18 16:30:00+01:00 177.869995 0.621451 178.975369
How it works
When the base exchange is closed and the underlying is still trading, afterquote builds a synthetic quote by decomposing each underlying bar into two multiplicative legs:
- Gap return — inter-bar move (underlying open vs its previous close), scaled by leverage
- Intra return — intra-bar move (underlying close vs its open), scaled by leverage
For cross-currency pairs (e.g. a GBp ETP tracking a USD stock), the FX rate is fetched and applied as a separate 1× leg — so leverage applies only to the underlying's return, not the currency move.
Every synthetic candle grows from a single anchor (the base's last close), so the chain is continuous and High ≥ max(Open, Close) ≥ Low holds by construction.
Usage
Synthetic quote
pair = SecurityPair("3TSL.L", "TSLA")
pair.info()
base_security underlying_security base_is_live leverage base_close_time base_close_price adj_percent_return quote_price
quote_time
2026-06-19 00:59:00+01:00 3TSL.L TSLA False 3 2026-06-18 16:30:00+01:00 177.869995 0.621451 178.975369
pair.pricing()
Impl_Open Impl_High Impl_Low Impl_Close
Datetime
2026-06-18 16:30:00+01:00 177.869995 178.077587 177.733974 178.070422
2026-06-18 16:31:00+01:00 178.070422 178.185074 177.941470 177.941470
2026-06-18 16:32:00+01:00 177.927178 177.962971 177.769626 177.884217
2026-06-18 16:33:00+01:00 177.884217 177.934294 177.619327 177.741023
2026-06-18 16:34:00+01:00 177.762466 178.141756 177.762466 177.991464
...
2026-06-19 00:59:00+01:00 178.946613 179.061640 178.946613 178.975369
[509 rows × 4 columns]
Confidence band
pair.info(confidence=0.95)
base_security underlying_security base_is_live leverage base_close_time base_close_price adj_percent_return quote_price lower_bound upper_bound
quote_time
2026-06-19 00:59:00+01:00 3TSL.L TSLA False 3 2026-06-18 16:30:00+01:00 177.869995 0.621451 178.975369 176.420 181.530
lower_bound / upper_bound come from the empirical distribution of past prediction errors — no Gaussian assumption. The band is asymmetric when errors are skewed.
Benchmark
from afterquote import benchmark, metrics
results = benchmark(pair, days=90)
print(results)
base_close synth_open actual_open residual direction_correct
2026-03-26 148.320007 163.052340 152.440002 10.612338 True
2026-03-27 152.440002 144.918760 161.800003 -16.881240 False
2026-03-28 161.800003 174.338120 168.220001 6.118119 True
2026-03-31 168.220001 159.774480 163.559998 -3.785518 True
2026-04-01 163.559998 141.832900 129.680008 12.152892 True
...
2026-06-18 177.869995 179.341200 178.240005 1.101195 True
[62 rows × 5 columns]
metrics(results)
{'rmse': 74.1, 'mae': 58.3, 'direction_correct': 0.71, 'tracking_error': 61.2, 'n': 62}
The model called the direction right 71% of the time over 62 sessions.
Correlation health check
pair.correlation()
# 0.7612
Pearson daily-return correlation between base and underlying over the last 90 days. Emits UserWarning when |corr| < 0.5.
Portfolio P&L
holdings.csv:
base,underlying,quantity
3TSL.L,TSLA,1000
3USL.L,SPY,500
from afterquote import portfolio_pnl
portfolio_pnl("holdings.csv")
base underlying quantity base_close_price quote_price pnl
3TSL.L TSLA 1000.0 177.869995 178.975369 1105.37
3USL.L SPY 500.0 312.540001 313.706240 583.12
TOTAL 1500.0 NaN NaN 1688.49
Point-in-time queries
All methods accept as_of for historical reconstruction — no look-ahead bias:
pair.info(as_of=pd.Timestamp("2026-06-18 20:00:00-04:00"))
pair.pricing(as_of=pd.Timestamp("2026-06-18 20:00:00-04:00"))
CLI
afterquote 3TSL.L TSLA # synthetic quote
afterquote 3TSL.L TSLA --confidence 0.95 # with confidence band
afterquote 3TSL.L TSLA --pricing # full OHLC bars
afterquote 3TSL.L TSLA --benchmark # backtest + metrics
afterquote 3TSL.L TSLA --correlation # correlation check
afterquote 3TSL.L TSLA --as-of "2026-06-18 20:00-04:00" # historical query
afterquote --holdings holdings.csv # portfolio P&L
$ afterquote 3TSL.L TSLA --benchmark
base_close synth_open actual_open residual direction_correct
2026-03-26 148.320007 163.052340 152.440002 10.612338 True
...
2026-06-18 177.869995 179.341200 178.240005 1.101195 True
rmse: 74.1
mae: 58.3
direction_correct: 0.71
tracking_error: 61.2
n: 62
$ afterquote 3TSL.L TSLA --correlation
correlation: 0.7612
Demo pairs
| Pair | Leverage | FX | Use case |
|---|---|---|---|
3TSL.L / TSLA |
3× | GBp → USD | Both leverage and FX active — the full model |
3USL.L / SPY |
3× | — | Leverage only — same-currency baseline |
Testing
pip install -e ".[test]"
pytest tests/ # 68 unit tests, mocked, ~0.4s — no network calls
pytest tests/ --runlive # + live yfinance validation
Contributing
Issues and PRs welcome. — Junaid
License
MIT. See LICENSE.
Project details
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 afterquote-1.0.0.tar.gz.
File metadata
- Download URL: afterquote-1.0.0.tar.gz
- Upload date:
- Size: 23.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
067053586a1fe6dd143419ea11e9d64cd3e9d94f21e93dac69b47d256d019c7d
|
|
| MD5 |
45a46a2c55ef68cc52acda34c33e719a
|
|
| BLAKE2b-256 |
c6a735c3faeda54f9e5c2edf28a39dfa36ab46baca6d290d9586e868891ac5a5
|
File details
Details for the file afterquote-1.0.0-py3-none-any.whl.
File metadata
- Download URL: afterquote-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dba07942cf9b1dbd9e382b68614d96aa250077e3f0eab0fbe75847a097ac376a
|
|
| MD5 |
c91f1b7cda96fcf2cb420ef708e3d83c
|
|
| BLAKE2b-256 |
815680e293bde3b2e49d4e3f59bfa9c17837d75b23f0b32dd67ddead45790bcd
|