A Python backtesting framework for quantitative trading strategies
Project description
StratCraft
A Python backtesting framework for building and evaluating quantitative trading strategies. StratCraft provides a clean, decorator-based API for defining indicators, handling multi-symbol data, and running event-driven backtests with portfolio tracking and performance analytics.
Features
- Event-driven backtesting — step-by-step simulation with clean separation of
initialize()andstep_forward()hooks - Multi-symbol support — buy/sell across multiple tickers in a single call
- Decorator toolkit —
@broadcast,@rolling,@grouping,@availablefor composable indicator computation - Portfolio management — track cash, equity, open positions, stop-loss and take-profit triggers
- Performance metrics — Sharpe ratio, Sortino ratio, Calmar ratio, max drawdown, win rate, profit factor, and more
- Interactive charts — Plotly-based equity curve with trade entry markers and optional additional panels
- Data screening — filter and rank symbols by criteria within
step_forward()
Installation
pip install stratcraft
Or install from source:
git clone https://github.com/kuanhungwang/stratcraft.git
cd stratcraft
pip install -e .
Quick Start
from stratcraft import Strategy, DataHandler
from datetime import datetime
class SMAStrategy(Strategy):
def initialize(self):
# Load price data and compute indicators here
self.data = {
'open': open_df,
'high': high_df,
'low': low_df,
'close': close_df,
}
self.data['sma20'] = close_df.rolling(20).mean()
def step_forward(self, data: DataHandler):
close = data['close'] # latest close (scalar or Series)
sma20 = data['sma20']
if close > sma20:
self.buy(symbol='AAPL', value=10_000)
else:
self.sell(symbol='AAPL', value=10_000)
strategy = SMAStrategy(initial_capital=100_000)
strategy.run(
start_date=datetime(2022, 1, 1),
end_date=datetime(2023, 12, 31),
data_length=2, # rows of history passed to step_forward
)
Core Concepts
Strategy lifecycle
| Method | When called | Purpose |
|---|---|---|
initialize() |
Once before the loop | Load data, compute indicators, set parameters |
step_forward(data) |
Every trading day | Implement trading logic, call buy()/sell() |
Buying and selling
# By quantity
self.buy(symbol='AAPL', quantity=100)
self.sell(symbol='AAPL', quantity=100)
# By dollar value
self.buy(symbol='AAPL', value=10_000)
# With stop-loss and take-profit (percentage of entry price)
self.buy(symbol='AAPL', quantity=100, stop_loss_percent=5, take_profit_percent=10)
# Multiple symbols at once
self.buy(symbol=['AAPL', 'MSFT'], value=[5_000, 5_000])
Accessing historical data inside step_forward
def step_forward(self, data: DataHandler):
latest_close = data['close'] # latest bar
prev_close = data[('close', -2)] # bar before latest
latest_sma = data['sma20']
Set
data_lengthinstrategy.run()to the maximum look-back depth you need.
Trailing stop-loss
from stratcraft import TrailingStopLoss, Direction
trade = self.buy(symbol='AAPL', quantity=100)
if trade:
trade.stop_loss = TrailingStopLoss(
price=trade.entry_price - 1.0, # initial stop price
distance=1.0, # trail distance
threshold=trade.entry_price + 1.0,
direction=Direction.LONG,
)
Call trade.stop_loss.reset_price(current_price) each bar to move the stop up.
Screening and ranking symbols
def step_forward(self, data: DataHandler):
# Filter symbols passing all boolean criteria
candidates = data.screen(['criteria_momentum', 'criteria_volume'])
# Rank by a field, optionally from a filtered subset
top5 = data.highest('momentum_score', n=5, tickers=candidates)
bot5 = data.lowest('volatility', n=5)
Prefix keys with signal_, criteria_, screen_, or filter_ in self.data — StratCraft will warn you during run() if any never triggered.
Portfolio queries inside step_forward
cash = self.portfolio.cash
equity = self.portfolio.equity
live = self.portfolio.live_trades()
cost_aapl = self.portfolio.cost(symbol='AAPL')
mkt_val = self.portfolio.current_market_value()
invest_ratio = self.portfolio.invest_ratio()
days = self.days_since_last_trade()
Decorators
Decorators live in stratcraft.decorators and are designed to be composed.
@broadcast
Applies a single-symbol (Series) function across all columns of a DataFrame:
from stratcraft.decorators import broadcast
import ta
@broadcast
def RSI(price):
return ta.momentum.RSIIndicator(price).rsi()
rsi = RSI(close_df) # returns DataFrame with same columns as close_df
@rolling
Turns a scalar reduction into a rolling-window Series/DataFrame:
from stratcraft.decorators import rolling
@rolling(window=14)
def avg_range(high, low):
return (high - low).mean()
atr = avg_range(high_df, low_df)
Compose with @broadcast for multi-symbol rolling indicators:
@broadcast
@rolling(window=20)
def momentum(price):
return (price.iloc[-1] / price.iloc[0]) - 1
@grouping
Applies a function to user-defined groups of symbols and returns a group-level DataFrame:
from stratcraft.decorators import grouping
sector = {'Technology': ['AAPL', 'MSFT'], 'Finance': ['JPM', 'BAC']}
@grouping(groups=sector)
def sector_return(price):
return price.pct_change().mean(axis=1)
sector_ret = sector_return(close_df) # columns: Technology, Finance
@available
Aligns low-frequency data (e.g. quarterly earnings) to a daily time series, exposing only data that would have been available on each date:
from stratcraft.decorators import available, broadcast
@broadcast
@available(looping_dates=close.index, length=1)
def daily_eps(eps):
return eps.iloc[-1]
eps_daily = daily_eps(fundamental['is_eps'], available_date=fundamental['fillingDate'])
pe = close / eps_daily
Performance Analysis
from stratcraft.metrics import Metrics
trade_df = strategy.portfolio.trade_history()
pl_df = strategy.portfolio.pl_history()
m = Metrics(trade_df, pl_df)
# Full metrics dict
results = m.metrics()
# Concise subset: cumulative return, annual return, win rate, volatility, Sharpe, max drawdown, # trades
results = m.metrics(concise=True)
Metrics.pretty_print(results)
Metrics computed:
| Metric | Description |
|---|---|
| Cumulative returns | Total return over the period |
| Annual return | Annualised CAGR |
| Annual volatility | Annualised std dev of daily returns |
| Sharpe ratio | Annual return / annual volatility |
| Sortino ratio | Annual return / downside deviation |
| Calmar ratio | Annual return / max drawdown |
| Max drawdown | Largest peak-to-trough decline |
| Omega ratio | Sum of gains / sum of losses above threshold |
| Stability | R² of linear fit on cumulative returns |
| Win rate | Fraction of closed trades that are profitable |
| Profit factor | Total wins / total losses |
| Avg win / loss trade p/l | Mean P&L of winning/losing trades |
| Skew / Kurtosis | Distribution shape of daily returns |
| Tail ratio | 95th percentile return / 5th percentile return |
Interactive chart
fig = m.chart_history()
fig.show()
# With additional panels
fig = m.chart_history(additional_chart_data={
'price_data': {'AAPL': aapl_df},
'indicators': {'RSI': rsi_series, 'SMA20': sma_series},
'cash_ratio': pl_df['cash'] / pl_df['equity'],
})
fig.write_html('backtest.html')
Project Structure
stratcraft/
├── stratcraft.py # Core: Strategy, Portfolio, Trade, MarketHandler, DataHandler
├── decorators.py # @broadcast, @rolling, @grouping, @available
├── metrics.py # Metrics class with performance analytics and Plotly charts
├── util.py # Helper utilities (case-insensitive access, date range, symbol alignment)
├── examples.py # API usage reference
└── examples/ # Complete runnable strategy examples
├── strategy1_single_stock_technical.py
├── strategy2_multi_stock_technical.py
├── strategy3_multi_position_technical.py
├── strategy4_single_stock_technical_fundamental.py
├── strategy5_multi_stock_technical_fundamental.py
└── strategy6_comparing_index.py
Requirements
- Python >= 3.10
- pandas >= 1.5
- numpy >= 1.23
- plotly >= 5.0
License
MIT
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 stratcraft-0.1.0.tar.gz.
File metadata
- Download URL: stratcraft-0.1.0.tar.gz
- Upload date:
- Size: 6.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c458e645c69595b6610f8fa598d95469ad1dcd16edb6d8e4b1d52e9046dcd030
|
|
| MD5 |
166bd7ab1178b7aeee6c801499ab177e
|
|
| BLAKE2b-256 |
d418fba7a675fd945110416e54809ad6425aecba8f820cea1e7f234f70601b14
|
File details
Details for the file stratcraft-0.1.0-py3-none-any.whl.
File metadata
- Download URL: stratcraft-0.1.0-py3-none-any.whl
- Upload date:
- Size: 5.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ff920a9940d121f855c1bc877184e467b5bf8121e0401ecc8ba22223600835c
|
|
| MD5 |
d0e67c036a5470999139b0ac995f0cbd
|
|
| BLAKE2b-256 |
352eae2bd95f1f22a401047dc8479d9e28fdccd0225b90f98e0b803b5230bed4
|