Strict-ordering bar feed for backtests and live strategies. Lookahead-proof by construction.
Project description
lightcone
Strict-ordering bar feed for backtests and live trading strategies. Lookahead-proof by construction.
Only the past light cone influences the present.
Why this exists
Every major Python backtest framework has known lookahead pitfalls. The classic failure mode: each bar's "scene" / "context" object holds references to mutable state. Future bars mutate that state. By the time your trading loop reads bars[10].scene.zone.broken, the value reflects bars 11..N — your trading decision read the future.
Tests can catch known lookahead patterns. They cannot catch the unknown ones.
The structural approach: make it impossible for the strategy to see a bar before it has confirmed processing all earlier bars. Then lookahead bugs are not testable failures — they are architectural impossibilities.
That's lightcone.
How it works
- Bars are delivered one at a time through a feed.
- Each delivery returns an opaque token. The strategy MUST call
confirm(token)before requesting the next bar. - Each bar is wrapped in a BarView that exposes only the fields the strategy declared in advance.
- Backtest and live use the same API. Only the data source changes.
Install
pip install lightcone-feed
Zero dependencies. Python 3.9+.
Quick start
from lightcone import Lightcone, OHLCV, FeedExhausted, from_ohlcv_rows
# Build a feed from Binance/HL-style OHLCV rows
feed = from_ohlcv_rows(
{("BTC", "5m"): btc_rows, ("ETH", "5m"): eth_rows},
config=OHLCV,
)
while True:
try:
bar, key, token = feed.next_bar()
except FeedExhausted:
break
# Your strategy reads only declared fields
if bar.close > bar.open and bar.volume > some_threshold:
decide_long(asset=key, ts=bar.ts)
feed.confirm(token) # ← MUST call before requesting next bar
Try to peek at a field you didn't declare → FieldNotDeclared. Try to skip confirm() and request the next bar → NotConfirmed. Try to confirm with the wrong token → BadToken.
Configuration profiles
from lightcone import CLOSE_ONLY, OHLCV, FULL_TAPE, custom
CLOSE_ONLY # → ts + close. Discretionary / human-trader replay.
OHLCV # → ts + OHLCV. Standard algorithmic backtest.
FULL_TAPE # → OHLCV + n_trades + taker_buy. Microstructure.
cfg = custom("close", "volume", "n_trades")
cfg = custom("close", extras=["funding_rate"])
Multi-asset / multi-timeframe
feed = Lightcone(streams={
("BTC", "5m"): btc_5m,
("BTC", "15m"): btc_15m,
("ETH", "5m"): eth_5m,
})
Bars across all streams come out in strict timestamp order via a min-heap, with deterministic tie-breaking. Same API for one stream or fifty.
Fill simulation (for limit / stop strategies)
from lightcone import Order, Side, OrderType, simulate_fill
fill = simulate_fill(
Order(Side.BUY, qty=1.0, order_type=OrderType.LIMIT, price=97.0),
bar,
slippage_bps=2.0,
)
The fill simulator reads the bar's full OHLC range — that's separate from the strategy's declared-field view, so your decision logic can't peek at high/low while the fill logic still uses them realistically.
Hardening
- Bars are frozen dataclasses; immutable post-construction
Bar.extrasis auto-frozen viaMappingProxyType(external mutation can't leak in)BarViewblocks all underscore-prefixed access — noview._bar.highescape hatchFieldNotDeclareddoes NOT inherit fromAttributeError, sohasattr()cannot silently swallow it- Tokens are 16 random bytes, compared with
secrets.compare_digest - Stream sequences are snapshotted to tuples — caller mutation can't affect the feed
- Strictly ascending timestamps required within each stream
Performance
| Workload | Throughput |
|---|---|
| 1M bars, single stream | ~400k bars/sec |
| 1M bars, 10 streams via heap | ~370k bars/sec |
| Per-bar overhead | ~2.5 microseconds |
| Memory | ~80 bytes / Bar |
The feed will never be your bottleneck. Strategy logic dominates by 10-100x.
What lightcone is NOT
- Not a full backtest framework. It's the data layer. Your strategy code, P&L tracking, and reporting live outside.
- Not thread-safe. Single-threaded use only. If parallelizing, give each thread its own feed.
- Not a training pipeline. Model training is offline batch work that uses the full timeline. Use lightcone for inference (backtest and live).
- Not a sandbox. A determined caller can read the feed's internal state. The contract catches accidents, not adversaries.
Documentation
| Doc | Read it for |
|---|---|
docs/CONTRACT.md |
The seven protocol rules. Read first. |
docs/API.md |
Reference for every public symbol. |
docs/ARCHITECTURE.md |
Why the package is designed the way it is. |
docs/EXAMPLES.md |
Backtest, multi-asset, fill sim, walk-forward recipes. |
docs/LIVE.md |
Wiring lightcone to a live WebSocket data source. |
Testing
pip install -e ".[dev]"
pytest lightcone/tests/
95 tests, ~14s runtime, 100% line coverage on production modules.
License
Apache-2.0. See LICENSE.
Contributing
See CONTRIBUTING.md. lightcone is small and focused — read that first.
Project details
Release history Release notifications | RSS feed
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 lightcone_feed-0.1.0.tar.gz.
File metadata
- Download URL: lightcone_feed-0.1.0.tar.gz
- Upload date:
- Size: 22.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fbde9286345ccbc0206871f4b6abdbb4927b0b57b2026a66cb99e4bebcee634
|
|
| MD5 |
9676ac000ce77a3e54f32c0806028a4f
|
|
| BLAKE2b-256 |
df40e28ec31a7ed7e8cc5c125424c332f64b54f365493dce8e7b2cfbe8675f38
|
Provenance
The following attestation bundles were made for lightcone_feed-0.1.0.tar.gz:
Publisher:
publish.yml on JaggerTheBoss/lightcone-feed
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lightcone_feed-0.1.0.tar.gz -
Subject digest:
3fbde9286345ccbc0206871f4b6abdbb4927b0b57b2026a66cb99e4bebcee634 - Sigstore transparency entry: 1540805973
- Sigstore integration time:
-
Permalink:
JaggerTheBoss/lightcone-feed@88f43f57d225f30cf06040a5f2fc8a9864766275 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JaggerTheBoss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88f43f57d225f30cf06040a5f2fc8a9864766275 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file lightcone_feed-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lightcone_feed-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81849a8d44b5d39fbb3124cc7dc29c477fd9f2f8f633c7e4316aa46950ad0b12
|
|
| MD5 |
4f5d941696dbf8ee33378af7d18f0dd9
|
|
| BLAKE2b-256 |
bbb026cd23941b80ed27d69a2e18a8d52b321e62919ee188da79e7caf0cfbd11
|
Provenance
The following attestation bundles were made for lightcone_feed-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on JaggerTheBoss/lightcone-feed
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lightcone_feed-0.1.0-py3-none-any.whl -
Subject digest:
81849a8d44b5d39fbb3124cc7dc29c477fd9f2f8f633c7e4316aa46950ad0b12 - Sigstore transparency entry: 1540806056
- Sigstore integration time:
-
Permalink:
JaggerTheBoss/lightcone-feed@88f43f57d225f30cf06040a5f2fc8a9864766275 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JaggerTheBoss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88f43f57d225f30cf06040a5f2fc8a9864766275 -
Trigger Event:
workflow_dispatch
-
Statement type: