How to Implement Position Sizing with Ollama: Kelly Criterion Calculator

Build a Kelly Criterion calculator using Ollama for optimal position sizing in trading. Step-by-step implementation with Python code and practical examples.

Ever wondered why some traders consistently outperform while others blow up their accounts? The secret often lies in position sizing, not stock picking. Meet the Kelly Criterion – a mathematical formula that tells you exactly how much to bet on each trade. Today, we'll build a smart Kelly Criterion calculator using Ollama that adapts to your trading style.

What is the Kelly Criterion and Why Does It Matter?

The Kelly Criterion calculates the optimal position size for any bet with known odds and probabilities. Developed by John Kelly at Bell Labs in 1956, this formula maximizes long-term growth while minimizing the risk of ruin.

The Kelly Formula Explained

The basic Kelly formula is:

f = (bp - q) / b

Where:

  • f = fraction of capital to bet
  • b = odds received (decimal odds - 1)
  • p = probability of winning
  • q = probability of losing (1 - p)

For trading, we modify this to:

f = (Win Rate × Average Win - Loss Rate × Average Loss) / Average Win

Why Use Ollama for Kelly Criterion Calculations?

Ollama provides several advantages for implementing position sizing algorithms:

  1. Local Processing: No API costs or data privacy concerns
  2. Custom Models: Train on your specific trading data
  3. Real-time Analysis: Process market data instantly
  4. Adaptability: Adjust parameters based on market conditions

Prerequisites and Setup

Before we start coding, ensure you have:

  • Python 3.8 or higher
  • Ollama installed locally
  • Basic understanding of trading concepts
  • Historical trading data (optional)

Install Required Dependencies

pip install ollama pandas numpy matplotlib requests

Download and Run Ollama Model

# Download a suitable model for calculations
ollama pull llama3.1:8b

# Start Ollama service
ollama serve

Building the Kelly Criterion Calculator

Let's create a comprehensive Kelly Criterion calculator that integrates with Ollama for intelligent position sizing.

Step 1: Create the Base Calculator Class

import ollama
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
import json

class KellyCriterionCalculator:
    def __init__(self, model_name: str = "llama3.1:8b"):
        """
        Initialize Kelly Criterion calculator with Ollama integration
        
        Args:
            model_name: Ollama model to use for analysis
        """
        self.model_name = model_name
        self.client = ollama.Client()
        
    def calculate_kelly_fraction(self, 
                               win_rate: float, 
                               avg_win: float, 
                               avg_loss: float) -> float:
        """
        Calculate Kelly fraction for position sizing
        
        Args:
            win_rate: Probability of winning (0-1)
            avg_win: Average winning trade return
            avg_loss: Average losing trade return (positive value)
            
        Returns:
            Kelly fraction (0-1)
        """
        if avg_win <= 0 or avg_loss <= 0:
            return 0.0
            
        # Standard Kelly formula for trading
        expectancy = (win_rate * avg_win) - ((1 - win_rate) * avg_loss)
        kelly_fraction = expectancy / avg_win
        
        # Cap at 25% for safety
        return min(max(kelly_fraction, 0), 0.25)
    
    def fractional_kelly(self, kelly_fraction: float, fraction: float = 0.5) -> float:
        """
        Apply fractional Kelly for reduced risk
        
        Args:
            kelly_fraction: Full Kelly fraction
            fraction: Fraction to apply (0.25 = quarter Kelly)
            
        Returns:
            Fractional Kelly value
        """
        return kelly_fraction * fraction

Step 2: Add Ollama Integration for Market Analysis

    def analyze_market_conditions(self, symbol: str, timeframe: str = "1d") -> Dict:
        """
        Use Ollama to analyze market conditions for Kelly adjustments
        
        Args:
            symbol: Trading symbol (e.g., "AAPL")
            timeframe: Analysis timeframe
            
        Returns:
            Market analysis dictionary
        """
        prompt = f"""
        Analyze the current market conditions for {symbol} over {timeframe} timeframe.
        Consider the following factors:
        1. Market volatility
        2. Trend strength
        3. Support/resistance levels
        4. Overall market sentiment
        
        Provide a risk assessment score from 1-10 (10 being highest risk) and recommend
        a Kelly fraction adjustment (0.5-1.0 multiplier).
        
        Return your analysis in JSON format with keys:
        - risk_score (1-10)
        - volatility_level (low/medium/high)
        - trend_strength (weak/moderate/strong)
        - kelly_multiplier (0.5-1.0)
        - reasoning (brief explanation)
        """
        
        try:
            response = self.client.chat(
                model=self.model_name,
                messages=[{"role": "user", "content": prompt}]
            )
            
            # Parse JSON response
            analysis = json.loads(response['message']['content'])
            return analysis
            
        except Exception as e:
            print(f"Error in market analysis: {e}")
            return {
                "risk_score": 5,
                "volatility_level": "medium",
                "trend_strength": "moderate",
                "kelly_multiplier": 0.75,
                "reasoning": "Default analysis due to error"
            }

Step 3: Implement Advanced Position Sizing

    def calculate_position_size(self, 
                              capital: float,
                              win_rate: float,
                              avg_win: float,
                              avg_loss: float,
                              symbol: str = None,
                              use_market_analysis: bool = True) -> Dict:
        """
        Calculate optimal position size using Kelly Criterion
        
        Args:
            capital: Total available capital
            win_rate: Historical win rate (0-1)
            avg_win: Average winning percentage
            avg_loss: Average losing percentage
            symbol: Trading symbol for market analysis
            use_market_analysis: Whether to use Ollama for market analysis
            
        Returns:
            Position sizing recommendations
        """
        # Calculate base Kelly fraction
        kelly_fraction = self.calculate_kelly_fraction(win_rate, avg_win, avg_loss)
        
        # Apply market analysis if requested
        market_multiplier = 1.0
        market_analysis = None
        
        if use_market_analysis and symbol:
            market_analysis = self.analyze_market_conditions(symbol)
            market_multiplier = market_analysis.get("kelly_multiplier", 1.0)
        
        # Calculate different Kelly variants
        full_kelly = kelly_fraction * market_multiplier
        half_kelly = self.fractional_kelly(full_kelly, 0.5)
        quarter_kelly = self.fractional_kelly(full_kelly, 0.25)
        
        # Calculate position sizes
        results = {
            "kelly_fraction": kelly_fraction,
            "market_adjusted_kelly": full_kelly,
            "position_sizes": {
                "full_kelly": capital * full_kelly,
                "half_kelly": capital * half_kelly,
                "quarter_kelly": capital * quarter_kelly,
                "conservative": capital * 0.02  # 2% rule
            },
            "percentages": {
                "full_kelly": full_kelly * 100,
                "half_kelly": half_kelly * 100,
                "quarter_kelly": quarter_kelly * 100,
                "conservative": 2.0
            },
            "market_analysis": market_analysis,
            "expectancy": (win_rate * avg_win) - ((1 - win_rate) * avg_loss)
        }
        
        return results

Step 4: Create Backtesting Functionality

    def backtest_kelly_strategy(self, 
                               trades_data: List[Dict],
                               initial_capital: float = 10000) -> Dict:
        """
        Backtest Kelly Criterion strategy performance
        
        Args:
            trades_data: List of trade dictionaries with 'return' and 'outcome'
            initial_capital: Starting capital
            
        Returns:
            Backtest results
        """
        # Calculate historical statistics
        wins = [trade['return'] for trade in trades_data if trade['outcome'] == 'win']
        losses = [abs(trade['return']) for trade in trades_data if trade['outcome'] == 'loss']
        
        win_rate = len(wins) / len(trades_data)
        avg_win = np.mean(wins) if wins else 0
        avg_loss = np.mean(losses) if losses else 0
        
        # Calculate Kelly fraction
        kelly_fraction = self.calculate_kelly_fraction(win_rate, avg_win, avg_loss)
        
        # Simulate trading with Kelly sizing
        capital = initial_capital
        capital_history = [capital]
        
        for trade in trades_data:
            position_size = capital * kelly_fraction
            trade_return = trade['return']
            
            if trade['outcome'] == 'win':
                capital += position_size * (trade_return / 100)
            else:
                capital -= position_size * (abs(trade_return) / 100)
            
            capital_history.append(capital)
        
        # Calculate performance metrics
        total_return = (capital - initial_capital) / initial_capital * 100
        max_drawdown = self._calculate_max_drawdown(capital_history)
        
        return {
            "initial_capital": initial_capital,
            "final_capital": capital,
            "total_return": total_return,
            "max_drawdown": max_drawdown,
            "kelly_fraction": kelly_fraction,
            "win_rate": win_rate,
            "avg_win": avg_win,
            "avg_loss": avg_loss,
            "capital_history": capital_history
        }
    
    def _calculate_max_drawdown(self, capital_history: List[float]) -> float:
        """Calculate maximum drawdown from capital history"""
        peak = capital_history[0]
        max_dd = 0
        
        for capital in capital_history:
            if capital > peak:
                peak = capital
            
            drawdown = (peak - capital) / peak * 100
            max_dd = max(max_dd, drawdown)
        
        return max_dd

Practical Implementation Examples

Example 1: Basic Kelly Calculation

# Initialize calculator
kelly_calc = KellyCriterionCalculator()

# Example trading statistics
win_rate = 0.6  # 60% win rate
avg_win = 8.5   # Average 8.5% gain
avg_loss = 4.2  # Average 4.2% loss

# Calculate position sizes
capital = 50000  # $50,000 account
results = kelly_calc.calculate_position_size(
    capital=capital,
    win_rate=win_rate,
    avg_win=avg_win,
    avg_loss=avg_loss,
    symbol="AAPL",
    use_market_analysis=True
)

print(f"Kelly Fraction: {results['kelly_fraction']:.3f}")
print(f"Recommended Position Sizes:")
print(f"  Full Kelly: ${results['position_sizes']['full_kelly']:,.2f}")
print(f"  Half Kelly: ${results['position_sizes']['half_kelly']:,.2f}")
print(f"  Quarter Kelly: ${results['position_sizes']['quarter_kelly']:,.2f}")

Example 2: Backtesting with Historical Data

# Sample trading data
trades_data = [
    {"return": 12.5, "outcome": "win"},
    {"return": -5.2, "outcome": "loss"},
    {"return": 8.7, "outcome": "win"},
    {"return": -3.1, "outcome": "loss"},
    {"return": 15.2, "outcome": "win"},
    # Add more trades...
]

# Run backtest
backtest_results = kelly_calc.backtest_kelly_strategy(
    trades_data=trades_data,
    initial_capital=10000
)

print(f"Backtest Results:")
print(f"  Total Return: {backtest_results['total_return']:.2f}%")
print(f"  Max Drawdown: {backtest_results['max_drawdown']:.2f}%")
print(f"  Kelly Fraction: {backtest_results['kelly_fraction']:.3f}")

Advanced Features and Customization

Dynamic Kelly Adjustments

The calculator can adapt to changing market conditions:

def dynamic_kelly_adjustment(self, base_kelly: float, market_conditions: Dict) -> float:
    """
    Adjust Kelly fraction based on market conditions
    
    Args:
        base_kelly: Base Kelly fraction
        market_conditions: Market analysis from Ollama
        
    Returns:
        Adjusted Kelly fraction
    """
    risk_score = market_conditions.get("risk_score", 5)
    volatility = market_conditions.get("volatility_level", "medium")
    
    # Reduce Kelly fraction in high-risk environments
    if risk_score > 7:
        adjustment = 0.5
    elif risk_score > 5:
        adjustment = 0.75
    else:
        adjustment = 1.0
    
    # Further adjust for volatility
    if volatility == "high":
        adjustment *= 0.8
    elif volatility == "low":
        adjustment *= 1.2
    
    return base_kelly * adjustment

Portfolio-Level Kelly Implementation

def portfolio_kelly_allocation(self, 
                             positions: List[Dict],
                             total_capital: float) -> Dict:
    """
    Calculate Kelly-optimal allocation across multiple positions
    
    Args:
        positions: List of position dictionaries with statistics
        total_capital: Total portfolio capital
        
    Returns:
        Optimal allocation dictionary
    """
    allocations = {}
    total_kelly = 0
    
    # Calculate individual Kelly fractions
    for position in positions:
        kelly_fraction = self.calculate_kelly_fraction(
            position['win_rate'],
            position['avg_win'],
            position['avg_loss']
        )
        
        allocations[position['symbol']] = {
            'kelly_fraction': kelly_fraction,
            'raw_allocation': total_capital * kelly_fraction
        }
        total_kelly += kelly_fraction
    
    # Normalize if total Kelly exceeds 100%
    if total_kelly > 1.0:
        for symbol in allocations:
            allocations[symbol]['normalized_allocation'] = (
                allocations[symbol]['raw_allocation'] / total_kelly
            )
    
    return allocations

Risk Management and Safety Features

Kelly Fraction Limits

Always implement safeguards to prevent excessive position sizing:

def apply_risk_limits(self, kelly_fraction: float, max_position: float = 0.25) -> float:
    """
    Apply maximum position size limits
    
    Args:
        kelly_fraction: Calculated Kelly fraction
        max_position: Maximum allowed position size
        
    Returns:
        Risk-adjusted Kelly fraction
    """
    # Never exceed maximum position limit
    if kelly_fraction > max_position:
        print(f"Warning: Kelly fraction ({kelly_fraction:.3f}) exceeds maximum ({max_position:.3f})")
        return max_position
    
    # Avoid negative Kelly (indicates negative expectancy)
    if kelly_fraction < 0:
        print("Warning: Negative Kelly fraction indicates negative expectancy")
        return 0.0
    
    return kelly_fraction

Stop-Loss Integration

def kelly_with_stop_loss(self, 
                        kelly_fraction: float,
                        stop_loss_pct: float,
                        max_risk_per_trade: float = 0.02) -> float:
    """
    Adjust Kelly fraction based on stop-loss level
    
    Args:
        kelly_fraction: Base Kelly fraction
        stop_loss_pct: Stop-loss percentage
        max_risk_per_trade: Maximum risk per trade
        
    Returns:
        Adjusted position size
    """
    # Calculate position size based on stop-loss risk
    risk_adjusted_size = max_risk_per_trade / (stop_loss_pct / 100)
    
    # Use smaller of Kelly fraction or risk-adjusted size
    return min(kelly_fraction, risk_adjusted_size)

Performance Optimization Tips

Efficient Ollama Usage

  1. Cache Analysis Results: Store market analysis for reuse
  2. Batch Requests: Process multiple symbols together
  3. Model Selection: Use appropriate model size for your needs

Memory Management

def optimize_calculations(self, trade_history: pd.DataFrame) -> Dict:
    """
    Optimize Kelly calculations for large datasets
    
    Args:
        trade_history: DataFrame with trade data
        
    Returns:
        Optimized calculation results
    """
    # Use vectorized operations for speed
    wins = trade_history[trade_history['outcome'] == 'win']['return']
    losses = trade_history[trade_history['outcome'] == 'loss']['return'].abs()
    
    # Calculate statistics using numpy for efficiency
    win_rate = len(wins) / len(trade_history)
    avg_win = wins.mean()
    avg_loss = losses.mean()
    
    return {
        'win_rate': win_rate,
        'avg_win': avg_win,
        'avg_loss': avg_loss,
        'kelly_fraction': self.calculate_kelly_fraction(win_rate, avg_win, avg_loss)
    }

Common Pitfalls and Solutions

Overconfidence in Historical Data

Historical performance doesn't guarantee future results. Always:

  • Use out-of-sample testing
  • Apply conservative position sizing
  • Monitor performance continuously

Ignoring Transaction Costs

Factor in trading costs when calculating returns:

def adjust_for_costs(self, gross_return: float, 
                    transaction_cost: float = 0.001) -> float:
    """
    Adjust returns for transaction costs
    
    Args:
        gross_return: Return before costs
        transaction_cost: Transaction cost as decimal
        
    Returns:
        Net return after costs
    """
    return gross_return - (2 * transaction_cost)  # Buy and sell costs

Market Regime Changes

Markets change, and your Kelly calculations should too:

def rolling_kelly_calculation(self, 
                             trade_data: pd.DataFrame,
                             window_size: int = 50) -> pd.Series:
    """
    Calculate rolling Kelly fraction to adapt to changing conditions
    
    Args:
        trade_data: Historical trade data
        window_size: Rolling window size
        
    Returns:
        Series of Kelly fractions over time
    """
    kelly_values = []
    
    for i in range(window_size, len(trade_data)):
        window_data = trade_data.iloc[i-window_size:i]
        
        wins = window_data[window_data['outcome'] == 'win']['return']
        losses = window_data[window_data['outcome'] == 'loss']['return'].abs()
        
        win_rate = len(wins) / len(window_data)
        avg_win = wins.mean() if len(wins) > 0 else 0
        avg_loss = losses.mean() if len(losses) > 0 else 0
        
        kelly_fraction = self.calculate_kelly_fraction(win_rate, avg_win, avg_loss)
        kelly_values.append(kelly_fraction)
    
    return pd.Series(kelly_values, index=trade_data.index[window_size:])

Deployment and Integration

Web Interface Integration

Create a simple web interface for your Kelly calculator:

<!-- Reference for web deployment -->
<div id="kelly-calculator">
    <h3>Kelly Criterion Calculator</h3>
    <form id="kelly-form">
        <input type="number" id="capital" placeholder="Total Capital" required>
        <input type="number" id="win-rate" placeholder="Win Rate (%)" required>
        <input type="number" id="avg-win" placeholder="Average Win (%)" required>
        <input type="number" id="avg-loss" placeholder="Average Loss (%)" required>
        <button type="submit">Calculate Position Size</button>
    </form>
    <div id="results"></div>
</div>

API Integration

Build a REST API for your Kelly calculator:

# Reference for API deployment
from flask import Flask, request, jsonify

app = Flask(__name__)
kelly_calc = KellyCriterionCalculator()

@app.route('/calculate-kelly', methods=['POST'])
def calculate_kelly_api():
    data = request.json
    
    results = kelly_calc.calculate_position_size(
        capital=data['capital'],
        win_rate=data['win_rate'] / 100,
        avg_win=data['avg_win'],
        avg_loss=data['avg_loss']
    )
    
    return jsonify(results)

Conclusion

The Kelly Criterion provides a mathematically sound approach to position sizing that can dramatically improve your trading performance. By integrating Ollama for market analysis, you create an adaptive system that responds to changing market conditions.

Key benefits of this implementation:

  • Optimal position sizing based on your historical performance
  • Market-aware adjustments using Ollama's analysis capabilities
  • Risk management through fractional Kelly and safety limits
  • Backtesting capabilities to validate your strategy

Remember to start with conservative position sizes and gradually increase as you gain confidence in your system. The Kelly Criterion is powerful, but it requires accurate inputs and disciplined execution.

Ready to implement Kelly Criterion position sizing in your trading strategy? Start with the basic calculator and gradually add the advanced features as you become more comfortable with the system.

For more advanced trading strategies and risk management techniques, explore our comprehensive trading algorithm series.