The full quant workflow in one framework: build strategies, vector & event-driven backtest at scale, compare in a single dashboard, and deploy the winner 🚀
Investing Algorithm Framework is a Python framework that covers the entire quant workflow: define a strategy once, vector-backtest thousands of parameter variants to find promising signals, narrow down with a storage layer that ranks 10k+ results in milliseconds, validate the winners in a realistic event-driven simulation, compare everything in a single interactive HTML dashboard, and deploy the best performer live, all with the same TradingStrategy class, no code rewrites between stages.
Most quant frameworks stop at "here's your backtest result." You get a number, maybe a chart, and then you're on your own figuring out which strategy variant is actually better, whether the result is robust across time windows, and how to go from research to production. This framework closes that gap.
Want to see this in practice? Check out the
examples/tutorial/: a series of runnable notebooks that walk you through every stage: defining a strategy, visualizing its signals, sweeping parameters across rolling windows, detecting overfitting with Monte Carlo permutation tests, filtering and ranking with the storage layer, and deploying the winner.
Features
- 📊 30+ Metrics — CAGR, Sharpe, Sortino, Calmar, VaR, CVaR, Max DD, Recovery & more
- 🧮 Cross-Sectional Pipelines — Rank, filter and score entire universes of symbols every iteration with a tidy factor table
- ⚡ Vector Backtesting for Signal Analysis — Quickly test your strategy logic on historical data to see how signals would have behaved before committing to full event-driven backtests
- 🏃 Event-Driven Backtesting — Once promising strategies are identified via vector backtests, run full event-driven backtests to simulate realistic execution and portfolio management
- 🔀 Permutation Testing / Monte Carlo Simulations — Assess the statistical robustness of your strategies by running them across randomized market scenarios to see how often your results could occur by chance
- 🚀 Deployment — Once the best strategy is identified through backtesting and comparison, deploy it to production locally or in the cloud (AWS Lambda / Azure Functions) to start live trading
- ⚔️ Multi-Strategy Comparison — Rank, filter & compare strategies in a single interactive report
- 🪟 Multi-Window Robustness — Test across different time periods with window coverage analysis
- 📈 Equity & Drawdown Charts — Overlay equity curves, rolling Sharpe, drawdown & return distributions
- 🗓️ Monthly Heatmaps & Yearly Returns — Calendar heatmap per strategy with return/growth toggles
- 🎯 Return Scenario Projections — Good, average, bad & very bad year projections from backtest data
- 📉 Benchmark Comparison — Beat-rate analysis vs Buy & Hold, DCA, risk-free & custom benchmarks
- 📄 One-Click HTML Report — Self-contained file, no server, dark & light theme, shareable
- 📦 Custom
.iafbtBacktest Bundle Format — An explicit, versioned, compressed, language-portable container (zstd + msgpack with magic-byte header) plus a separate parquet index for fast filtering without loading. ~21× smaller and ~27× fewer files than standard filebased directory layouts, with parallel I/O for fast load/save of large amounts of backtests. - 🗄️ Tiered Backtest Storage Layer — Manage thousands of
.iafbtbundles with a Tier-1 SQLite index (sub-100 ms ranks/filters over 10k+ backtests), a swappableBacktestStoreprotocol (LocalDirStore,LocalTieredStore), content-addressed Tier-3 OHLCV deduplication, and a CLI (iaf index/iaf list/iaf rank/iaf migrate-store) that plugs straight into the HTML dashboard. - 🌐 Load External Data — Fetch CSV, JSON, or Parquet from any URL with caching and auto-refresh
- � Per-Market Deposit Schedules & Portfolio Sync — Declare recurring or one-shot external cash flows on a market with
deposit_schedule=/auto_sync=True. Backtests simulate the deposits; live mode reconciles with the broker — samecontext.sync_portfolio()API in both modes. - 📝 Record Custom Variables — Track any indicator or metric during backtests with
context.record() - ⏱️ Signal Cooldowns: Throttle whipsaw with declarative
CooldownRules: per-symbol or portfolio-wide, side-aware (trigger="sell",blocks="buy"), enforced identically by the vector and event-driven engines - 🚀 Build → Backtest → Deploy — Local dev, cloud deploy (AWS / Azure), or monetize on Finterion
Strategy Definition
Declare what data your strategy needs and when to buy or sell as a TradingStrategy subclass — the framework wires up data loading, signal evaluation, order execution, position management, and reporting around it. The same class runs unchanged in vector backtests, event-driven backtests, paper trading and live.
Want strategy ideas to start from? Check out
examples/strategies_showcase/: a collection of runnable strategy templates (trend following, mean reversion, cross-sectional momentum, multi-factor, pairs trading, and more).
Risk and execution behaviour are expressed as declarative rule lists rather than ad-hoc code paths, so the engines can enforce them identically across modes:
position_sizes:PositionSizeper symbol (fixed amount or percentage of portfolio).stop_losses/take_profits:StopLossRule/TakeProfitRulewith fixed or trailing thresholds and partial-exitsell_percentage.scaling_rules:ScalingRulefor pyramiding (scale_in_percentage=[…],max_entries, per-symbolcooldown_in_bars).cooldowns:CooldownRuleto throttle whipsaw — per-symbol or portfolio-wide, side-aware (e.g.trigger="sell", blocks="buy", bars=12). Enforced bar-for-bar in both the vector and event-driven engines.trading_costs:TradingCostper symbol (fees, slippage, fixed costs).
from investing_algorithm_framework import (
TradingStrategy,
PositionSize,
ScalingRule,
StopLossRule,
TakeProfitRule,
CooldownRule,
TradingCost,
)
class MyStrategy(TradingStrategy):
symbols = ["BTC", "ETH"]
position_sizes = [
PositionSize(symbol="BTC", percentage_of_portfolio=20),
PositionSize(symbol="ETH", percentage_of_portfolio=20),
]
stop_losses = [
StopLossRule(symbol="BTC", percentage_threshold=5, trailing=True),
StopLossRule(symbol="ETH", percentage_threshold=5, trailing=True),
]
take_profits = [
TakeProfitRule(
symbol="BTC", percentage_threshold=10, sell_percentage=50,
),
TakeProfitRule(
symbol="ETH", percentage_threshold=10, sell_percentage=50,
),
]
scaling_rules = [
ScalingRule(
symbol="BTC", max_entries=3, scale_in_percentage=[50, 25],
),
ScalingRule(
symbol="ETH", max_entries=3, scale_in_percentage=[50, 25],
),
]
cooldowns = [
CooldownRule(symbol="BTC", trigger="sell", blocks="buy", bars=12),
CooldownRule(trigger="any", blocks="any", bars=2),
]
trading_costs = [
TradingCost(symbol="BTC", fee_percentage=0.1),
TradingCost(symbol="ETH", fee_percentage=0.1),
]
def generate_buy_signals(self, data):
...
def generate_sell_signals(self, data):
...Backtesting Engines
Polars-powered vectorized signal evaluation. Compare thousands of strategies side by side, sweep parameter grids, run multi-window robustness checks, rank by key metrics and surface your top candidates in seconds — all before committing to a full event-driven simulation.
Once you've narrowed down promising strategies, run them through a full event-driven simulation. Pluggable slippage and fill models, partial fills, and a complete simulation blotter — using the same code path you'll deploy live.
Backtest Analysis & Dashboard
Every backtest produces a self-contained HTML dashboard — open it in any browser, share with teammates, archive it. No server, no Jupyter, no dependencies. Compare strategies side-by-side, drill into trades, and capture your reasoning as you go.
- Self-contained HTML reports — equity curves, drawdowns, trade lists, monthly returns, side-by-side strategy comparison
- Built-in MCP server — let Copilot, Claude, or any MCP-compatible agent query your backtests, rank strategies, and reason over trades through
investing-algorithm-framework mcp - Notes keeping — annotate every backtest with hypotheses, observations and conclusions; notes travel with the report so your research is never lost
💡 Want state-of-the-art analytics, publishable reports, ranking across thousands of runs and AI agents that do the analysis for you? Partner with our analytics integration partners below — they pick up where the local
report.htmlleaves off.
Every backtest API — vector or event-driven — returns the same Backtest object, which the BacktestReport consumes directly. So whether you're iterating over an in-memory list or a folder of persisted .iafbt bundles, the path to the dashboard is the same:
from investing_algorithm_framework import BacktestReport
# --- Single event-driven backtest ---
backtest = app.run_backtest(backtest_date_range=date_range)
BacktestReport(backtests=[backtest]).save("event_report.html")
# --- A sweep of vector backtests (parameter grid / multi-window) ---
backtests = app.run_vector_backtests(
strategies=[StrategyA(), StrategyB(), StrategyC()],
backtest_date_ranges=[range_2022, range_2023, range_2024],
n_workers=-1,
backtest_storage_directory="./my-backtests/", # persists .iafbt bundles
show_progress=True,
)
BacktestReport(backtests=backtests).save("sweep_report.html")
# --- Or: load a folder of bundles back later (parallel decode) ---
report = BacktestReport.open(
directory_path="./my-backtests/",
workers=-1,
show_progress=True,
)
report.save("from_disk_report.html")For sweeps that grow into the thousands, combine this with the Backtest Storage Layer below — rank in SQLite first, then load only the winners into the report:
from investing_algorithm_framework import BacktestReport
from investing_algorithm_framework.cli.index_command import (
build_index, rank_index,
)
from investing_algorithm_framework.services.backtest_store import (
LocalDirStore,
)
# 1. Build (or refresh) the Tier-1 SQLite index over the folder of bundles.
build_index("./my-backtests/")
# 2. Pick the top 25 by Sharpe straight from SQLite — no Parquet decoded.
top = rank_index(
"./my-backtests/",
by="sharpe_ratio",
where="summary_number_of_trades > 50",
limit=25,
)
# 3. Materialise only those 25 bundles through the BacktestStore protocol.
store = LocalDirStore("./my-backtests/")
winners = [store.open(row["bundle_path"]) for row in top]
# 4. Render a focused dashboard with just the winners.
BacktestReport(backtests=winners).save("top25_by_sharpe.html")Backtest Storage Layer — scale to thousands of backtests
Once you start sweeping parameter grids and walk-forward windows, a flat folder of .iafbt bundles stops scaling: every comparison re-decodes multi-MB Parquet metric blobs just to read a Sharpe number. The storage layer fixes that with three tiers behind a single BacktestStore protocol:
- Tier-1 — SQLite index (
index.sqlite): one row per bundle with every scalar fromBacktestSummaryMetricspromoted to its own column. Ranking 10k+ bundles becomes a sub-100 ms SQL query — no.iafbtis opened. - Tier-2 —
BacktestStoreadapters:LocalDirStore(flat folder of bundles) orLocalTieredStore(hive-partitioned layout). Same handle-based API, swap the implementation without touching call sites. - Tier-3 — content-addressed OHLCV chunks: SHA-256 deduped per-symbol OHLCV blobs shared across every bundle that references them.
garbage_collect_ohlcv()reclaims orphans.
A CLI ties it all together: iaf index builds/refreshes the Tier-1 SQLite, iaf list / iaf rank query it, and iaf migrate-store moves a whole collection between store kinds in one command.
from investing_algorithm_framework import BacktestReport
from investing_algorithm_framework.cli.index_command import (
build_index, rank_index,
)
from investing_algorithm_framework.services.backtest_store import (
LocalDirStore,
)
# 1. Build (or refresh) the Tier-1 SQLite index over a folder of .iafbt bundles.
build_index("./my-backtests/") # equivalent to: iaf index ./my-backtests/
# 2. Pick the top 20 by Sharpe straight from SQLite — no Parquet decoded.
top = rank_index(
"./my-backtests/",
by="sharpe_ratio",
where="summary_number_of_trades > 50",
limit=20,
)
# 3. Materialise just those 20 bundles through the BacktestStore protocol.
store = LocalDirStore("./my-backtests/")
backtests = [store.open(row["bundle_path"]) for row in top]
# 4. Feed them straight into the HTML dashboard.
BacktestReport(backtests=backtests).save("top20.html")Or from the shell:
iaf index ./my-backtests/
iaf rank ./my-backtests/ --by sharpe_ratio --where "summary_number_of_trades > 50" -n 20
iaf list ./my-backtests/ --sort calmar_ratio --json
iaf migrate-store --from local-dir --src ./my-backtests/ \
--to local-tiered --dst ./tiered/→ End-to-end runnable example: examples/storage_layer_demo/
Live Trading
Once a strategy proves itself in backtests, deploy it with the same code path you backtested. Connect to any exchange — use the built-in CCXT integration, or plug in your own OrderExecutor for brokers, FIX gateways, or any custom venue. Run locally, in Docker, or deploy serverless to AWS Lambda or Azure Functions. Built-in portfolio tracking, position management, order persistence, and automatic state recovery.
- No code rewrites — your
TradingStrategyruns identically in backtest, paper trading and live - Cloud deploy —
investing-algorithm-framework init --type aws_lambda/--type azure_function - Multiple exchanges & venues — CCXT integration out of the box (Binance, Bitvavo, Coinbase, Kraken …), or plug in your own
OrderExecutorfor any broker / FIX / custom venue - Portfolio persistence — trades, orders and positions survive restarts
Marketplace Integration
Publish your winning strategies to the Finterion marketplace and monetize them. Investors subscribe to your bot, you earn a recurring revenue share — the framework handles the technical integration.
Usage and Installation
To get started, install the framework and scaffold a new project:
pip install investing-algorithm-framework
# Generate project structure
investing-algorithm-framework init
# Or for cloud deployment
investing-algorithm-framework init --type aws_lambda
investing-algorithm-framework init --type azure_functionThe documentation provides guides and API reference. The quick start will walk you through your first strategy.
Creating a Strategy
The framework is designed around the TradingStrategy class. You define what data your strategy needs and when to buy or sell — the framework handles execution, position management, and reporting.
from typing import Dict, Any
import pandas as pd
from pyindicators import ema, rsi, crossover, crossunder
from investing_algorithm_framework import (
TradingStrategy, DataSource, TimeUnit, DataType,
PositionSize, ScalingRule, StopLossRule, CooldownRule,
)
class RSIEMACrossoverStrategy(TradingStrategy):
"""
EMA crossover + RSI filter strategy with position scaling and stop losses.
Buy when RSI is oversold AND a recent EMA crossover occurred.
Sell when RSI is overbought AND a recent EMA crossunder occurred.
Scale into winners, trail a stop loss, and let the framework handle the rest.
"""
time_unit = TimeUnit.HOUR
interval = 2
symbols = ["BTC", "ETH"]
data_sources = [
DataSource(
identifier="BTC_ohlcv", symbol="BTC/EUR",
data_type=DataType.OHLCV, time_frame="2h",
market="BITVAVO", pandas=True, warmup_window=100,
),
DataSource(
identifier="ETH_ohlcv", symbol="ETH/EUR",
data_type=DataType.OHLCV, time_frame="2h",
market="BITVAVO", pandas=True, warmup_window=100,
),
]
# Risk management
position_sizes = [
PositionSize(symbol="BTC", percentage_of_portfolio=20),
PositionSize(symbol="ETH", percentage_of_portfolio=20),
]
scaling_rules = [
ScalingRule(
symbol="BTC", max_entries=3,
scale_in_percentage=[50, 25], cooldown_in_bars=5,
),
ScalingRule(
symbol="ETH", max_entries=3,
scale_in_percentage=[50, 25], cooldown_in_bars=5,
),
]
stop_losses = [
StopLossRule(
symbol="BTC", percentage_threshold=5,
sell_percentage=100, trailing=True,
),
StopLossRule(
symbol="ETH", percentage_threshold=5,
sell_percentage=100, trailing=True,
),
]
# Signal throttling: after a stop-out / sell, block re-entries on
# the same symbol for 12 bars, plus a portfolio-wide breather of
# 2 bars after any order to avoid same-bar pile-ups.
cooldowns = [
CooldownRule(
symbol="BTC", trigger="sell", blocks="buy", bars=12,
),
CooldownRule(
symbol="ETH", trigger="sell", blocks="buy", bars=12,
),
CooldownRule(trigger="any", blocks="any", bars=2),
]
def generate_buy_signals(
self, data: Dict[str, Any]
) -> Dict[str, pd.Series]:
signals = {}
for symbol in self.symbols:
df = data[f"{symbol}_ohlcv"]
ema_short = ema(df, period=12, source_column="Close",
result_column="ema_short")
ema_long = ema(ema_short, period=26, source_column="Close",
result_column="ema_long")
ema_cross = crossover(ema_long,
first_column="ema_short",
second_column="ema_long",
result_column="ema_crossover")
rsi_data = rsi(df, period=14, source_column="Close",
result_column="rsi")
rsi_oversold = rsi_data["rsi"] < 30
recent_crossover = (
ema_cross["ema_crossover"].rolling(window=10).max() > 0
)
signals[symbol] = (rsi_oversold & recent_crossover).fillna(False)
return signals
def generate_sell_signals(
self, data: Dict[str, Any]
) -> Dict[str, pd.Series]:
signals = {}
for symbol in self.symbols:
df = data[f"{symbol}_ohlcv"]
ema_short = ema(df, period=12, source_column="Close",
result_column="ema_short")
ema_long = ema(ema_short, period=26, source_column="Close",
result_column="ema_long")
ema_cross = crossunder(ema_long,
first_column="ema_short",
second_column="ema_long",
result_column="ema_crossunder")
rsi_data = rsi(df, period=14, source_column="Close",
result_column="rsi")
rsi_overbought = rsi_data["rsi"] >= 70
recent_crossunder = (
ema_cross["ema_crossunder"].rolling(window=10).max() > 0
)
signals[symbol] = (rsi_overbought & recent_crossunder).fillna(False)
return signalsCreate as many strategy variants as you want — different parameters, different indicators, different symbols — then backtest them all and compare in a single report.
Backtest Report Dashboard
Every backtest produces a single HTML file you can open in any browser, share with teammates, or archive. No server, no dependencies, no Jupyter required.
from investing_algorithm_framework import BacktestReport
# After running backtests
report = BacktestReport(backtest)
report.show() # Opens dashboard in your browser
# Or load previously saved backtests from disk
report = BacktestReport.open(directory_path="path/to/backtests")
report.show()
# Compare multiple strategies side by side
report = BacktestReport.open(backtests=[backtest_a, backtest_b, backtest_c])
report.show()
# Save as a self-contained HTML file
report.save("my_report.html")Overview page — KPI cards, key metrics ranking table, trading activity, return scenarios, equity curves, metric bar charts, monthly returns heatmap, return distributions, and window coverage matrix.
Strategy pages — Deep dive into each strategy with per-run equity curves, rolling Sharpe, drawdown, monthly/yearly returns, and portfolio summary.
Capabilities
| Backtest Report Dashboard | Self-contained HTML report with ranking tables, equity curves, metric charts, heatmaps, and strategy comparison |
| Event-Driven Backtesting | Realistic, order-by-order simulation |
| Vectorized Backtesting | Fast signal research and prototyping |
| Cross-Sectional Pipelines | Compute factors across many symbols at once — rank, filter and score universes per iteration |
| 50+ Metrics | CAGR, Sharpe, Sortino, max drawdown, win rate, profit factor, recovery factor, volatility, and more |
| Live Trading | Connect to exchanges via CCXT for real-time execution |
| Portfolio Management | Position tracking, trade management, persistence |
| Cloud Deployment | Deploy to AWS Lambda, Azure Functions, or run as a web service |
| Market Data Providers | Built-in providers for CCXT, Yahoo Finance, Alpha Vantage, and Polygon — or build your own |
| Load External Data | Fetch CSV, JSON, or Parquet from any URL with caching, date parsing, and pre/post-processing |
| Record Custom Variables | Track any indicator or metric during backtests with context.record() |
| Strategies | OHLCV, tickers, custom data — Polars and Pandas native |
| Extensible | Custom data providers, order executors, and strategy classes |
Plugins
| Plugin | Description |
|---|---|
| PyIndicators | Technical analysis indicators (EMA, RSI, MACD, etc.) |
| Finterion Plugin | Share and monetize strategies on Finterion's marketplace |
We welcome contributions! Open an issue, pick one up, or send a PR.
git clone https://github.com/coding-kitties/investing-algorithm-framework.git
cd investing-algorithm-framework
poetry install
# Run all tests
python -m unittest discover -s tests- Open an issue for bugs or ideas
- Read the Contributing Guide
- PRs go against the
devbranch
- Documentation — Guides and API reference
- Quick Start — Get up and running
- Discord — Chat and support
- Reddit — Strategy discussion
If you use this framework for real trading, do not risk money you are afraid to lose. Test thoroughly with backtesting first. Start small. We assume no responsibility for your investment results.
We want to thank all contributors to this project. A full list can be found in AUTHORS.md.
Finterion — Marketplace for trading bots. Monetize your strategies by publishing them on Finterion.
