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.
Metadata
Release files for afterquote 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| afterquote-1.0.0.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| afterquote-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.8 kB
Release files / afterquote-1.0.0.tar.gz
| Download URL | afterquote-1.0.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
067053586a1fe6dd143419ea11e9d64cd3e9d94f21e93dac69b47d256d019c7d
|
|
BLAKE2b-256 checksum How to use checksums |
c6a735c3faeda54f9e5c2edf28a39dfa36ab46baca6d290d9586e868891ac5a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / afterquote-1.0.0-py3-none-any.whl
| Download URL | afterquote-1.0.0-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dba07942cf9b1dbd9e382b68614d96aa250077e3f0eab0fbe75847a097ac376a
|
|
BLAKE2b-256 checksum How to use checksums |
815680e293bde3b2e49d4e3f59bfa9c17837d75b23f0b32dd67ddead45790bcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|