Skip to main content

Lightweight Python backtesting library for algorithmic trading strategies

Project description

Portfolio-lib ๐Ÿ“ˆ

Ultra-Lightweight Python Backtesting Library for Algorithmic Trading

Python Version License: MIT Package Size

Authors: Rahul Ashok, Pritham Devaprasad, Siddarth S, and Anish R

Portfolio-lib is a comprehensive, ultra-lightweight backtesting framework designed for developing and testing quantitative trading strategies. With minimal dependencies and maximum performance, it provides everything you need for professional algorithmic trading research.

๐Ÿš€ Key Features

โšก Ultra-Lightweight Architecture

  • < 500KB total package size - minimal memory footprint
  • Only essential dependencies: pandas, numpy, yfinance, scipy, matplotlib
  • Optimized for speed and efficiency

๐Ÿ“Š Comprehensive Technical Analysis

  • 129 Technical Indicators built-in
  • SMA, EMA, RSI, MACD, Bollinger Bands, Stochastic, ADX, and more
  • Custom indicator support with easy extensibility

๐Ÿ”„ Advanced Backtesting Engine

  • Event-driven backtesting architecture
  • Multi-asset portfolio simulation
  • Commission and slippage modeling
  • Real-time strategy execution

๐Ÿ“ˆ Professional Analytics

  • Advanced performance metrics (Sharpe, Sortino, Calmar ratios)
  • Risk management tools (VaR, CVaR, Maximum Drawdown)
  • Comprehensive trade analysis and reporting
  • Visual performance charts and equity curves

๐ŸŒ Data Integration

  • yfinance integration for real market data
  • Support for stocks, ETFs, forex, and cryptocurrencies
  • Custom data source support
  • Historical and real-time data handling

๐Ÿ“ฆ Installation

pip install portfolio-lib

Requirements

  • Python 3.8+
  • pandas >= 1.5.0
  • numpy >= 1.21.0
  • yfinance >= 0.2.0
  • scipy >= 1.9.0
  • matplotlib >= 3.5.0

๐ŸŽฏ Quick Start

Basic SMA Crossover Strategy

from portfolio_lib import BaseStrategy, Backtest, YFinanceDataFeed

class SMAStrategy(BaseStrategy):
    def __init__(self):
        super().__init__()
        self.symbols = ['AAPL', 'MSFT']
        self.start_date = '2020-01-01'
        self.end_date = '2023-12-31'
        self.fast_period = 10
        self.slow_period = 30
        
    def init_indicators(self):
        print(f"SMA Strategy initialized for {self.symbols}")
    
    def next(self):
        for symbol in self.symbols:
            prices = self.data[symbol]['Close']
            
            if len(prices) < self.slow_period:
                continue
                
            fast_sma = prices.rolling(self.fast_period).mean().iloc[-1]
            slow_sma = prices.rolling(self.slow_period).mean().iloc[-1]
            
            position = self.position(symbol)
            
            # Buy signal: fast SMA crosses above slow SMA
            if fast_sma > slow_sma and position is None:
                self.buy(symbol, 0.5)  # 50% of portfolio
                
            # Sell signal: fast SMA crosses below slow SMA  
            elif fast_sma < slow_sma and position is not None:
                self.sell(symbol)

# Run backtest
strategy = SMAStrategy()
backtest = Backtest(strategy, initial_cash=100000)
data_feed = YFinanceDataFeed(strategy.symbols)
backtest.add_data_source(data_feed)

results = backtest.run(strategy.start_date, strategy.end_date)
print(results.summary())

RSI Mean Reversion Strategy

from portfolio_lib import BaseStrategy, TechnicalIndicators

class RSIStrategy(BaseStrategy):
    def __init__(self):
        super().__init__()
        self.symbols = ['TSLA', 'NVDA']
        self.start_date = '2021-01-01'
        self.end_date = '2023-12-31'
        self.rsi_period = 14
        self.oversold = 30
        self.overbought = 70
        
    def next(self):
        for symbol in self.symbols:
            prices = self.data[symbol]['Close']
            
            if len(prices) < self.rsi_period + 1:
                continue
                
            rsi = TechnicalIndicators.rsi(prices, self.rsi_period)
            position = self.position(symbol)
            
            # Buy when oversold
            if rsi < self.oversold and position is None:
                self.buy(symbol, 0.3)
                
            # Sell when overbought
            elif rsi > self.overbought and position is not None:
                self.sell(symbol)

๐Ÿ“š Core Components

๐Ÿ”ง BaseStrategy

Base class for all trading strategies with built-in portfolio management:

class MyStrategy(BaseStrategy):
    def init_indicators(self):
        # Initialize your indicators here
        pass
        
    def next(self):
        # Your strategy logic for each bar
        # Access data: self.data[symbol]['Close']
        # Place orders: self.buy(symbol, size) / self.sell(symbol)
        # Check positions: self.position(symbol)
        pass

๐Ÿ’ฐ Portfolio Management

Automatic portfolio tracking with position management:

# Portfolio automatically tracks:
# - Cash balance
# - Active positions  
# - Trade history
# - Equity curve
# - Performance metrics

portfolio = results.portfolio
print(f"Total Equity: ${portfolio.total_equity:,.2f}")
print(f"Cash: ${portfolio.cash:,.2f}")
print(f"Positions: {len(portfolio.positions)}")

๐Ÿ“Š Technical Indicators

81+ built-in technical indicators:

from portfolio_lib import TechnicalIndicators

# Moving averages
sma = TechnicalIndicators.sma(prices, period=20)
ema = TechnicalIndicators.ema(prices, period=20)
wma = TechnicalIndicators.wma(prices, period=20)

# Oscillators
rsi = TechnicalIndicators.rsi(prices, period=14)
stoch = TechnicalIndicators.stochastic(high, low, close)
williams_r = TechnicalIndicators.williams_r(high, low, close)

# Trend indicators
macd = TechnicalIndicators.macd(prices)
adx = TechnicalIndicators.adx(high, low, close)
aroon = TechnicalIndicators.aroon(high, low)

# Volatility indicators
bb = TechnicalIndicators.bollinger_bands(prices)
atr = TechnicalIndicators.atr(high, low, close)

๐Ÿ“ˆ Performance Analytics

Comprehensive performance and risk metrics:

metrics = results.metrics

# Returns and performance
print(f"Total Return: {metrics.total_return:.2f}%")
print(f"Annualized Return: {metrics.annualized_return:.2f}%")
print(f"Volatility: {metrics.volatility:.2f}%")

# Risk metrics
print(f"Sharpe Ratio: {metrics.sharpe_ratio:.2f}")
print(f"Sortino Ratio: {metrics.sortino_ratio:.2f}")
print(f"Maximum Drawdown: {metrics.max_drawdown:.2f}%")

# Trading metrics
print(f"Win Rate: {metrics.win_rate:.2f}%")
print(f"Profit Factor: {metrics.profit_factor:.2f}")
print(f"Total Trades: {len(metrics.trades)}")

๐ŸŽ›๏ธ Built-in Strategies

Portfolio-lib includes professionally implemented strategies ready to use:

from portfolio_lib import (
    SMAStrategy, EMAStrategy, RSIStrategy, MACDStrategy,
    BollingerBandsStrategy, MeanReversionStrategy, MomentumStrategy
)

# Use built-in strategies directly
strategy = RSIStrategy(symbols=['AAPL'], rsi_period=14)
backtest = Backtest(strategy, initial_cash=100000)

๐Ÿ› ๏ธ Advanced Features

Multi-Asset Portfolio

class DiversifiedStrategy(BaseStrategy):
    def __init__(self):
        super().__init__()
        self.stocks = ['AAPL', 'MSFT', 'GOOGL']
        self.etfs = ['SPY', 'QQQ', 'IWM']
        self.symbols = self.stocks + self.etfs

Risk Management

from portfolio_lib import RiskMetrics

# Portfolio risk analysis
risk = RiskMetrics(portfolio)
var_95 = risk.value_at_risk(confidence=0.95)
cvar_95 = risk.conditional_var(confidence=0.95)

Custom Data Sources

class CustomDataFeed(DataFeed):
    def fetch_data(self, symbols, start_date, end_date):
        # Implement your custom data fetching logic
        return data_dict

๐Ÿ“Š Performance Metrics

Portfolio-lib calculates 20+ professional metrics:

Returns Risk Trading
Total Return Sharpe Ratio Win Rate
Annualized Return Sortino Ratio Profit Factor
CAGR Calmar Ratio Total Trades
Alpha Maximum Drawdown Average Trade
Beta Volatility Best/Worst Trade

๐Ÿ”ง Configuration

Strategy Parameters

class ConfigurableStrategy(BaseStrategy):
    def __init__(self, fast_ma=10, slow_ma=30, position_size=0.5):
        super().__init__()
        self.fast_ma = fast_ma
        self.slow_ma = slow_ma  
        self.position_size = position_size

Backtest Settings

backtest = Backtest(
    strategy=strategy,
    initial_cash=100000,
    commission=0.001,  # 0.1% commission
    slippage=0.0005    # 0.05% slippage
)

๐Ÿš€ Why Portfolio-lib?

๐Ÿƒโ€โ™‚๏ธ Speed & Efficiency

  • Ultra-lightweight: < 500KB package size
  • Minimal dependencies: Only essential libraries
  • Optimized algorithms: Maximum performance per operation
  • Memory efficient: Minimal RAM usage

๐Ÿ” Professional Features

  • 81+ technical indicators with professional implementations
  • Advanced risk metrics including VaR, CVaR, drawdown analysis
  • Multi-asset support for stocks, ETFs, forex, crypto
  • Real market data integration via yfinance

๐Ÿ›ก๏ธ Production Ready

  • Thoroughly tested with comprehensive unit tests
  • Well documented with clear examples and API reference
  • Active maintenance by experienced quant developers
  • Community support and regular updates

๐Ÿ“ˆ Research Focused

  • Academic rigor with proper statistical implementations
  • Publication ready results and charts
  • Extensible architecture for custom strategies
  • Professional reporting with detailed analytics

๐Ÿ“‹ Examples & Tutorials

Complete Example: Momentum Strategy

import pandas as pd
from portfolio_lib import BaseStrategy, Backtest, YFinanceDataFeed, TechnicalIndicators

class MomentumStrategy(BaseStrategy):
    def __init__(self):
        super().__init__()
        self.symbols = ['AAPL', 'MSFT', 'GOOGL', 'AMZN']
        self.start_date = '2020-01-01'
        self.end_date = '2023-12-31'
        self.lookback = 20
        self.top_n = 2
        
    def init_indicators(self):
        print("Momentum Strategy: Buy top performing stocks")
        
    def next(self):
        if len(self.data[self.symbols[0]]) < self.lookback:
            return
            
        # Calculate momentum for each symbol
        momentum_scores = {}
        for symbol in self.symbols:
            prices = self.data[symbol]['Close']
            momentum = (prices.iloc[-1] / prices.iloc[-self.lookback] - 1) * 100
            momentum_scores[symbol] = momentum
            
        # Sort by momentum
        sorted_symbols = sorted(momentum_scores.items(), 
                              key=lambda x: x[1], reverse=True)
        
        # Close positions not in top N
        for symbol in self.symbols:
            position = self.position(symbol)
            if position and symbol not in [s[0] for s in sorted_symbols[:self.top_n]]:
                self.sell(symbol)
                
        # Open positions in top N
        for symbol, score in sorted_symbols[:self.top_n]:
            position = self.position(symbol)
            if not position:
                self.buy(symbol, 1.0 / self.top_n)

# Run the strategy
strategy = MomentumStrategy()
backtest = Backtest(strategy, initial_cash=100000)
data_feed = YFinanceDataFeed(strategy.symbols)
backtest.add_data_source(data_feed)

results = backtest.run(strategy.start_date, strategy.end_date)

# Display comprehensive results
print("๐Ÿš€ MOMENTUM STRATEGY RESULTS")
print("=" * 40)
print(results.summary())

# Additional analysis
print("\\n๐Ÿ“Š DETAILED METRICS")
print(f"Number of trades: {len(results.portfolio.trades)}")
print(f"Average position size: {100/strategy.top_n:.1f}%")
print(f"Portfolio volatility: {results.metrics.volatility:.2f}%")
print(f"Best trade return: {max([t.net_value for t in results.portfolio.trades]):,.2f}")

๐Ÿค Contributing

Portfolio-lib is developed by Rahul Ashok, Pritham Devaprasad, Siddarth S, and Anish R.

We welcome contributions! Please see our contributing guidelines for details on:

  • Code standards and style
  • Testing requirements
  • Documentation guidelines
  • Issue reporting

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™‹โ€โ™€๏ธ Support

๐ŸŒŸ Star History

If you find Portfolio-lib useful, please give it a star! โญ


Built with โค๏ธ by Rahul Ashok, Pritham Devaprasad, Siddarth S, and Anish R

Portfolio-lib: Where lightweight meets powerful in algorithmic trading.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

portfolio_lib-1.1.0.tar.gz (47.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

portfolio_lib-1.1.0-py3-none-any.whl (34.8 kB view details)

Uploaded Python 3

File details

Details for the file portfolio_lib-1.1.0.tar.gz.

File metadata

  • Download URL: portfolio_lib-1.1.0.tar.gz
  • Upload date:
  • Size: 47.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.1

File hashes

Hashes for portfolio_lib-1.1.0.tar.gz
Algorithm Hash digest
SHA256 b424233f4552a01f1308f2f77a2bb6ca0162696fbf34f3fa8c8520db4d11745c
MD5 c3fd28f0b3a3fb351dbb4997d6e62197
BLAKE2b-256 0457b74dbb12fd163dd0a471e7f54efc289e721d1fd98ea96d3f3c86c994cec1

See more details on using hashes here.

File details

Details for the file portfolio_lib-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: portfolio_lib-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 34.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.1

File hashes

Hashes for portfolio_lib-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a633d33b96377ec6fef45c418e8fe55269b146fbb2dd556d124f07f116584d7
MD5 4fcc98e7827ccf6c41ddc858ef8ed05f
BLAKE2b-256 27fdd635c63b4866a8688a76605673309bcffeb8ac74fd009d64cf5c1f355f3c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page