Build strategies, backtest at scale, compare results, and deploy the winner without rewriting your strategy.
Investing Algorithm Framework is a Python framework for the complete quantitative trading workflow. Define a strategy once, explore it with vector backtests, validate it in an event-driven simulation, inspect the results in an interactive dashboard, and run the same strategy in paper or live trading.
v9.0.0 alpha is available. Install the prerelease explicitly:
pip install investing-algorithm-framework==9.0.0a17See the v9.0 release notes and changelog. The APIs below describe current v9 development.
- 🔁 Long and short trading: Define entry and exit signals once and run the same strategy in vector backtests, event-driven backtests, paper trading, and live trading.
- ⚡ Vector backtesting: Evaluate signals and sweep thousands of strategy variants quickly with Polars-powered execution.
- 🏃 Event-driven backtesting: Validate candidates bar by bar with realistic orders, fills, costs, and portfolio management.
- 🪟 Studies, universes, and backtest windows: Define the assets, assumptions, and rolling, anchored, holdout, walk-forward, time-OOS, or universe-OOS periods for each experiment.
- 🗂️ Open Backtest Format: Keep complete vector and event results in portable, versioned
.obtfbundles. - 🗄️ Tiered storage and indexing: Rank and filter 10,000+ backtests through SQLite without decoding full result bundles.
- 📊 80+ performance metrics: Analyze CAGR, Sharpe, Sortino, Calmar, VaR/CVaR, drawdown, recovery, and benchmark-relative returns.
- 📈 Interactive dashboard: Compare strategies, inspect trades and charts, review window coverage, and save a self-contained HTML report.
- 🔀 Monte Carlo testing: Measure how often randomized market paths match or outperform a strategy's observed results.
- 🧮 Cross-sectional pipelines: Rank, filter, and score entire universes of symbols during each strategy iteration.
- 🧠 Confluence scoring cards: Build explainable decisions from requirements, weighted evidence, vetoes, and score thresholds.
- 🛡️ Declarative risk rules: Configure sizing, exposure limits, scaling, stop losses, take profits, and signal cooldowns.
- 💸 Execution cost models: Apply percentage, fixed, basis-point, or volume-aware commission and slippage assumptions.
- 🔐 Portfolio and credentials management: Configure markets, balances, fees, paper trading, and environment-based credentials without hardcoding secrets.
- 🚀 Flexible deployment: Run locally, self-host, deploy to AWS Lambda or Azure Functions, or publish through Finterion.
- 🌐 Extensible integrations: Add custom data providers, order executors, storage adapters, metrics, strategies, and optimizers.
- 🤖 Built-in MCP server: Let compatible AI tools query backtests, compare strategies, inspect trades, and manage research notes.
↔️ Position modes: NETTING vs. HEDGE: Choose one net direction per symbol or maintain independent long and short legs with separate risk controls and P&L.
Strategy Definition
Strategies are TradingStrategy subclasses that declare their data, schedule,
signal logic, and risk rules. The framework handles data loading, order execution,
position management, persistence, and reporting around them.
from pyindicators import crossover, crossunder, ema, rsi
from investing_algorithm_framework import ConfluenceCard, CooldownRule, \
DataSource, DataType, EvidenceGroup, ExposureRule, Operator, \
PrimaryGroup, ScalingRule, Schedule, ScoreRule, SignalSide, \
StopLossRule, TakeProfitRule, TimeUnit, TradingStrategy, \
condition, PositionSize
def create_confluence_card(name, rsi_operator, rsi_value, cross_column):
return ConfluenceCard(
name=name,
primary=PrimaryGroup(name="RSI reversal", rules=(ScoreRule(
name=f"RSI {rsi_operator.value} {rsi_value}",
expression=condition("rsi", rsi_operator, value=rsi_value), points=3,
),), minimum_matches=1),
secondary=(EvidenceGroup(name="EMA confirmation", rules=(ScoreRule(
name="Recent EMA cross",
expression=condition(cross_column, Operator.GT, value=0), points=2,
),), minimum_score=2),),
minimum_score=5,
)
class RSIEMACrossoverStrategy(TradingStrategy):
schedule = Schedule.every(2, TimeUnit.HOUR)
symbols = ["BTC"]
data_sources = [DataSource(
identifier="BTC_ohlcv", symbol="BTC/EUR", data_type=DataType.OHLCV,
time_frame="2h", market="BITVAVO", pandas=True, warmup_window=100,
)]
# Portfolio and position risk controls.
exposure_rule = ExposureRule(max_portfolio_percentage=80)
position_sizes = [PositionSize(symbol="BTC", percentage_of_portfolio=20)]
scaling_rules = [ScalingRule(
symbol="BTC", 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,
)]
take_profits = [TakeProfitRule(
symbol="BTC", percentage_threshold=10, sell_percentage=50,
)]
cooldowns = [
CooldownRule(symbol="BTC", trigger="sell", blocks="buy", bars=12),
CooldownRule(trigger="any", blocks="any", bars=2),
]
# Weighted, explainable entry and exit decisions.
signal_cards = {
SignalSide.OPEN_LONG: create_confluence_card(
"Open long", Operator.LT, 30, "recent_crossover"),
SignalSide.CLOSE_LONG: create_confluence_card(
"Close long", Operator.GTE, 70, "recent_crossunder"),
SignalSide.OPEN_SHORT: create_confluence_card(
"Open short", Operator.GTE, 70, "recent_crossunder"),
SignalSide.CLOSE_SHORT: create_confluence_card(
"Close short", Operator.LT, 30, "recent_crossover"),
}
def prepare_signal_data(self, data):
"""Prepare the same card inputs for every execution mode."""
frame = data["BTC_ohlcv"].copy()
frame = ema(frame, "Close", 12, "ema_short")
frame = ema(frame, "Close", 26, "ema_long")
frame = crossover(frame, "ema_short", "ema_long", "ema_crossover")
frame = crossunder(frame, "ema_short", "ema_long", "ema_crossunder")
frame = rsi(frame, "Close", 14, "rsi")
frame["recent_crossover"] = frame["ema_crossover"].rolling(10).max()
frame["recent_crossunder"] = frame["ema_crossunder"].rolling(10).max()
return {"BTC": frame}The framework evaluates signal_cards against the prepared columns and creates
signals and decision traces automatically. prepare_signal_data is shared by
vector backtests, event-driven backtests, paper trading, and live trading, while
the declarative rules govern sizing, exposure, scaling, exits, and cooldowns in
each mode. The four cards cover long entry and exit plus short entry and cover.
Fees and slippage belong to the portfolio or backtest study because they describe a venue or scenario rather than signal logic.
See the strategy guide, the basic example, and the strategy showcase.
Backtesting
Backtests use a Study to describe the experiment and a
BacktestRunConfiguration to control execution and persistence.
from investing_algorithm_framework import BacktestRunConfiguration
results = app.run_backtests(
strategies=strategies,
study=training_study,
run_configuration=BacktestRunConfiguration(
backtest_storage_directory="./my-backtests",
n_workers=8,
memory_budget_mb=16_384,
min_available_memory_mb=4_096,
),
)
print(results.df)Both engines return a disk-backed BacktestIndex. Use the index for scalar
filtering and ranking, then load selected full results with
results.iter_backtests() or results.load_backtests().
The Polars-powered vector engine evaluates signal series in bulk. Use it for rapid signal research, parameter sweeps, large candidate sets, and early-stage filtering before running more expensive simulations.
The event engine processes market data bar by bar through the same strategy and order path used in live trading. Use it to validate execution behavior, fills, costs, portfolio changes, and interactions between strategies.
Study, Universe, and BacktestWindow support single-window tests, rolling
windows, anchored windows, holdouts, walk-forward k-fold validation, and
out-of-sample testing across time periods or universes. Execution assumptions
are captured with each study so results remain reproducible.
See the vector backtesting guide, the event backtesting guide, and the tutorial notebooks.
Backtest Storage
Each algorithm is stored as a versioned .obtf bundle using the
Open Backtest Format.
A bundle can contain studies, universes, windows, vector and event runs,
summaries, metrics, trades, orders, positions, snapshots, execution assumptions,
and Monte Carlo results.
The storage layer scales from local research to large result collections:
| Tier | Purpose |
|---|---|
| SQLite index | Rank and filter scalar metrics across 10,000+ results without decoding bundles |
BacktestStore |
Swap flat LocalDirStore and partitioned LocalTieredStore layouts |
| OHLCV chunks | Deduplicate content-addressed market data shared by multiple bundles |
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/See the storage layer example.
Deployment
The same strategy can run locally, in a container, as a web service, or in a serverless function. Portfolio state, orders, trades, and positions can persist across runs.
Connect to supported exchanges through CCXT or implement an OrderExecutor for
a broker, FIX gateway, or custom venue. Live mode evaluates schedules, loads
market data, creates orders, and maintains portfolio state continuously.
Paper trading exercises the live strategy path without sending real orders. Use it after event-driven validation to verify schedules, data feeds, credentials, and operational behavior in current market conditions.
Run the application on your own machine or infrastructure, package it in Docker, or scaffold AWS Lambda and Azure Functions projects from the CLI:
pip install investing-algorithm-framework
investing-algorithm-framework init
investing-algorithm-framework init --type aws_lambda
investing-algorithm-framework init --type azure_functionPublish validated strategies to the Finterion marketplace so investors can subscribe to them. The Finterion plugin handles the framework integration.
See the deployment guide and Finterion plugin.
Portfolio and Credentials Management
app.add_market() configures a portfolio and its market credentials together.
Set the trading currency, starting balance, fees, slippage, position mode, and
paper-trading behavior in one place:
app.add_market(
market="BITVAVO",
trading_symbol="EUR",
initial_balance=10_000,
fee_percentage=0.1,
paper_trading=True,
)Keep secrets outside source code with market-scoped environment variables:
BITVAVO_API_KEY=<your-api-key>
BITVAVO_SECRET_KEY=<your-api-secret>Explicit credentials from a secret manager can be registered with
MarketCredential. Deployment-level BITVAVO_OVERRIDE_* variables can enforce
credentials, paper mode, and managed balance regardless of values supplied by
the application.
Portfolio state, orders, positions, and trades persist across runs. Optional portfolio synchronization reconciles broker balances and supports recurring or one-off deposit schedules. Local paper trading requires no real credentials.
See Portfolio Configuration, Credential Management, and Portfolio Synchronization.
Dashboard
BacktestReport creates a self-contained interactive HTML dashboard. Compare
strategies, inspect equity and drawdown curves, review trades, analyze monthly
and yearly returns, check window coverage, and add research notes without
running a separate server.
from investing_algorithm_framework import BacktestReport
# Materialize only the selected results when working with a large index.
BacktestReport(
backtests=results.load_backtests(workers=1),
).save("backtest-report.html")
# Or reopen a directory of persisted bundles later.
BacktestReport.open(
directory_path="./my-backtests/",
workers=-1,
show_progress=True,
).save("backtest-report.html")The built-in MCP server lets compatible AI tools query stored backtests, compare
strategies, inspect trades, and work with report notes through
investing-algorithm-framework mcp.
See the report guide and MCP server guide.
Advanced Features
- Backtest optimization: plug in an ask/tell
StrategyOptimizer, budget candidate evaluations, and resume saved search state. - Long and short trading: use open/close signals for either side with fill-based P&L and collateral handling.
- Cross-sectional pipelines: rank, filter, and score a universe of symbols on each iteration.
- Confluence cards: combine primary conditions, weighted evidence, requirements, vetoes, and score thresholds.
- Decision traces: retain indicator values, rule outcomes, and scores for signals and no-op decisions.
- Risk rules: configure position sizing, exposure limits, scaling, stop losses, take profits, and signal cooldowns.
- Execution models: use percentage, fixed, basis-point, or volume-aware commission and slippage models.
- Monte Carlo testing: estimate whether performance could plausibly occur by chance.
- Metrics and benchmarks: analyze CAGR, Sharpe, Sortino, Calmar, VaR/CVaR, drawdown, recovery, and benchmark-relative performance.
- External data and custom variables: load cached CSV, JSON, or Parquet data and record strategy-specific values.
- Portfolio synchronization: model recurring or one-off deposits and reconcile live balances with a broker.
- Bounded execution: control workers, memory admission, checkpoints, progress, and failure handling for large sweeps.
See the advanced concepts documentation and backtest optimization guide.
Position Modes: NETTING vs. HEDGE
Portfolios use PositionMode.NETTING by default: each symbol has one net
direction, so a long and short cannot coexist. Enable PositionMode.HEDGE to
maintain independent long and short legs for the same symbol, each with its own
entry, scaling, stop-loss, take-profit, cooldown, and P&L.
from investing_algorithm_framework import PositionMode
app.add_market(
market="BITVAVO",
trading_symbol="EUR",
initial_balance=10_000,
position_mode=PositionMode.HEDGE,
)Both backtest engines support OPEN_LONG, CLOSE_LONG, OPEN_SHORT, and
CLOSE_SHORT independently in HEDGE mode. In NETTING mode,
flip_on_opposite_signal=True can close the current direction and open the
opposite direction on the same bar.
Live HEDGE trading requires an OrderExecutor and PortfolioProvider that
explicitly support it. The built-in CCXT adapters support NETTING only, so use
custom HEDGE-capable adapters for live execution.
Pipelines
Pipelines compute factors across many symbols in one pass and return a tidy cross-sectional table on each strategy iteration. Use them to define a tradeable universe, rank candidates, normalize factors, and construct multi-factor or risk-neutral signals without manually looping over symbols.
from investing_algorithm_framework import AverageDollarVolume, Pipeline, Returns
class MomentumScreener(Pipeline):
dollar_volume = AverageDollarVolume(window=30)
momentum = Returns(window=30)
universe = dollar_volume.top(100)
alpha = momentum.rank(mask=universe)
class MomentumStrategy(TradingStrategy):
pipelines = [MomentumScreener]
def generate_signals(self, context, data):
candidates = data["MomentumScreener"]
leaders = candidates.sort("alpha", descending=True).head(10)
# Yield Signal objects for the selected symbols.
...Built-in factors include returns, liquidity, moving averages, RSI, volatility, cross-sectional means, rolling beta, and neutralization. Factors compose with arithmetic, ranking, filtering, z-scoring, demeaning, winsorization, and grouped transforms.
See the Pipelines guide, event backtest integration, vector backtest integration, and live integration.
Monte Carlo Testing
Monte Carlo permutation testing helps distinguish a strategy's observed edge from results that randomized market paths could produce by chance. The framework runs the strategy on its original OHLCV data, creates randomized permutations, reruns the same strategy on each permutation, and compares the real metrics with the resulting null distributions.
Use app.run_monte_carlo_test(...) with a strategy, a BacktestDateRange, and
the desired number of permutations. The returned BacktestMonteCarloTest
contains the real metrics, metrics from every permuted run, the original and
permuted datasets, and p-values for metrics including:
- CAGR, Sharpe, Sortino, and Calmar ratios
- Profit factor, win rate, and win/loss ratio
- Annual volatility and maximum drawdown
- Average monthly return
Lower p-values indicate that fewer randomized runs matched or exceeded the observed result. They are evidence about statistical significance, not proof of future profitability. Use enough permutations for the precision you need and combine the result with out-of-sample and walk-forward validation.
See the tutorial notebooks for the robustness analysis workflow.
Plugins and Supported Libraries
| Integration | Purpose |
|---|---|
| PyIndicators | Technical indicators including EMA, RSI, and MACD |
| Finterion plugin | Publish and operate strategies on Finterion |
| CCXT | Exchange market data and live order execution |
| Pandas and Polars | Native tabular inputs for strategy and backtest workflows |
| Yahoo Finance, Alpha Vantage, and Polygon | Built-in market data providers |
| AWS and Azure | Optional state storage and serverless deployment integrations |
Custom data providers, order executors, storage adapters, strategies, metrics, and optimizers can be added without replacing the core workflow.
Contributions are welcome. Open an issue, choose an existing one, or submit a
pull request against the dev branch.
git clone https://github.com/coding-kitties/investing-algorithm-framework.git
cd investing-algorithm-framework
poetry install
python -m unittest discover -s testsDo not risk money you cannot afford to lose. Backtests and paper trading cannot guarantee future performance. Validate strategy behavior, execution assumptions, stored results, and operational safeguards before trading with real funds. The project and its contributors assume no responsibility for investment results.
Thank you to everyone who has contributed code, documentation, testing, ideas, and feedback. See AUTHORS.md for the contributor list.
Finterion is a marketplace for trading bots where strategy creators can publish and monetize their work.