Files
optimiz-rs/examples/polaroid_optimizr_integration.py
T
Melvin Alvarez f5f6005f80 feat(parallel): add GIL-free parallel DE with Rust objectives
- Implement RustObjective trait for GIL-free parallelization
- Add 5 benchmark functions: Sphere, Rosenbrock, Rastrigin, Ackley, Griewank
  * Each implements RustObjective with evaluate(), dimension(), global_optimum()
  * Exposed to Python with __call__ method

- Add parallel_differential_evolution_rust() function:
  * Uses Rayon for parallel population evaluation
  * Works with RustObjective implementations only
  * Eliminates Python GIL overhead for 10-100× speedup
  * Supports all DE strategies and adaptive parameters

- Create comprehensive examples:
  * parallel_de_benchmark.py: Performance benchmarks showing speedup
  * polaroid_optimizr_integration.py: 4 workflows combining Polaroid + OptimizR
    - Regime detection with HMM
    - Strategy parameter optimization
    - Portfolio risk analysis
    - Pairs trading pipeline

- Module integration:
  * Export benchmark functions in Python API
  * Export parallel_differential_evolution_rust
  * Update __init__.py and core.py with new functions

- Technical implementation:
  * RustObjective trait in src/rust_objectives.rs
  * Parallel evaluation uses par_iter() from Rayon
  * Per-thread RNG seeding for reproducibility
  * Maintains same API as standard DE for easy comparison

Part of Priority 2: Enable Rust parallelization (Enhancement Strategy)
Expected speedup: 10-100× on multi-core systems for pure Rust objectives
2026-01-03 00:03:29 +01:00

500 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Polaroid + OptimizR Integration Examples
========================================
Demonstrates workflows combining Polaroid's time-series operations with OptimizR's
optimization and statistical inference capabilities.
Workflows:
1. Regime Detection: Polaroid features → OptimizR HMM
2. Strategy Optimization: Polaroid backtesting → OptimizR DE
3. Risk Analysis: Polaroid data processing → OptimizR risk metrics
4. Pairs Trading: Combined feature engineering and parameter optimization
Prerequisites:
- Polaroid gRPC server running (or data files available)
- OptimizR installed with time-series helpers
"""
import numpy as np
import optimizr
from typing import List, Tuple
def workflow1_regime_detection_with_features():
"""
Workflow 1: Regime Detection with Feature Engineering
Uses OptimizR's time-series helpers (which could integrate with Polaroid's
lag/diff/pct_change operations) to prepare features for HMM regime detection.
"""
print("\n" + "=" * 70)
print("Workflow 1: Regime Detection with Feature Engineering")
print("=" * 70)
# Simulate price data (in production, this comes from Polaroid)
np.random.seed(42)
# Generate regime-switching prices
prices = [100.0]
regime = 0 # 0=bull, 1=bear, 2=sideways
for _ in range(200):
if np.random.random() < 0.05: # 5% chance of regime switch
regime = (regime + 1) % 3
if regime == 0: # Bull
ret = np.random.normal(0.001, 0.015)
elif regime == 1: # Bear
ret = np.random.normal(-0.001, 0.02)
else: # Sideways
ret = np.random.normal(0, 0.01)
prices.append(prices[-1] * (1 + ret))
# Step 1: Feature engineering with OptimizR helpers
print("\n1. Feature Engineering:")
features = optimizr.prepare_for_hmm_py(prices, lag_periods=[1, 2, 3])
print(f" Created feature matrix: {len(features)} rows × {len(features[0])} columns")
print(" Features: returns, log_returns, volatility, lag1, lag2, lag3")
# Step 2: Train HMM for regime detection
print("\n2. Training HMM (3 regimes):")
# Extract returns for HMM (first column of feature matrix)
returns = [row[0] for row in features]
# Initialize and train HMM
hmm = optimizr.HMM(n_states=3)
hmm.fit(returns, n_iterations=50, tolerance=1e-4)
print(f" Training complete after {50} iterations")
print(f" Log-likelihood: {hmm.log_likelihood(returns):.2f}")
# Step 3: Predict regimes
print("\n3. Regime Prediction:")
states = hmm.predict(returns)
# Analyze regime statistics
unique_states, counts = np.unique(states, return_counts=True)
print(f" Detected {len(unique_states)} regimes:")
for state, count in zip(unique_states, counts):
pct = count / len(states) * 100
print(f" - Regime {state}: {count} periods ({pct:.1f}%)")
# Step 4: Regime characteristics
print("\n4. Regime Characteristics:")
for state in unique_states:
regime_returns = [r for r, s in zip(returns, states) if s == state]
mean, std, skew, kurt, sharpe = optimizr.return_statistics_py(regime_returns)
print(f" Regime {state}:")
print(f" Mean return: {mean*100:.3f}% (annualized: {mean*252*100:.1f}%)")
print(f" Volatility: {std*100:.3f}% (annualized: {std*np.sqrt(252)*100:.1f}%)")
print(f" Sharpe: {sharpe:.2f}")
print("\n✅ Workflow 1 complete! Use regimes for regime-switching strategies.")
return states, returns
def workflow2_strategy_optimization():
"""
Workflow 2: Strategy Parameter Optimization
Uses Differential Evolution to optimize trading strategy parameters,
with Polaroid handling data operations and OptimizR handling optimization.
"""
print("\n" + "=" * 70)
print("Workflow 2: Moving Average Crossover Strategy Optimization")
print("=" * 70)
# Simulate OHLC data
np.random.seed(42)
n_days = 500
prices = [100.0]
for _ in range(n_days - 1):
ret = np.random.normal(0.0005, 0.02)
prices.append(prices[-1] * (1 + ret))
prices = np.array(prices)
def moving_average_strategy(params: List[float], prices: np.ndarray) -> float:
"""
Simulate MA crossover strategy.
params = [short_window, long_window, stop_loss]
Returns: negative Sharpe ratio (for minimization)
"""
short_win = int(params[0])
long_win = int(params[1])
stop_loss = params[2]
# Calculate moving averages
short_ma = np.convolve(prices, np.ones(short_win)/short_win, mode='valid')
long_ma = np.convolve(prices, np.ones(long_win)/long_win, mode='valid')
# Align arrays
n = min(len(short_ma), len(long_ma))
short_ma = short_ma[-n:]
long_ma = long_ma[-n:]
aligned_prices = prices[-n:]
# Generate signals
position = 0
returns = []
entry_price = 0
for i in range(1, n):
if short_ma[i] > long_ma[i] and short_ma[i-1] <= long_ma[i-1]:
# Buy signal
position = 1
entry_price = aligned_prices[i]
elif short_ma[i] < long_ma[i] and short_ma[i-1] >= long_ma[i-1]:
# Sell signal
position = 0
# Stop loss
if position == 1 and entry_price > 0:
drawdown = (aligned_prices[i] - entry_price) / entry_price
if drawdown < -stop_loss:
position = 0
# Calculate returns
if position == 1:
ret = (aligned_prices[i] - aligned_prices[i-1]) / aligned_prices[i-1]
returns.append(ret)
else:
returns.append(0)
if len(returns) < 10:
return 999.0 # Penalty for invalid parameters
# Calculate Sharpe ratio
mean_ret = np.mean(returns)
std_ret = np.std(returns)
if std_ret == 0:
return 999.0
sharpe = mean_ret / std_ret * np.sqrt(252)
return -sharpe # Negative for minimization
print("\n1. Setting up optimization:")
print(" Parameters: [short_window, long_window, stop_loss]")
print(" Bounds: short=[5, 50], long=[20, 200], stop_loss=[0.02, 0.15]")
# Define objective function for OptimizR
def objective(x: List[float]) -> float:
return moving_average_strategy(x, prices)
# Optimize with Differential Evolution
print("\n2. Running Differential Evolution:")
result = optimizr.differential_evolution(
objective,
bounds=[(5, 50), (20, 200), (0.02, 0.15)],
strategy="best1",
max_iterations=50,
population_size=20,
convergence_threshold=1e-6
)
print(f" Optimization complete!")
print(f" Best parameters:")
print(f" Short window: {int(result['x'][0])} days")
print(f" Long window: {int(result['x'][1])} days")
print(f" Stop loss: {result['x'][2]*100:.1f}%")
print(f" Best Sharpe ratio: {-result['fun']:.3f}")
print(f" Iterations: {result['nit']}")
print("\n✅ Workflow 2 complete! Optimal strategy parameters found.")
return result
def workflow3_risk_analysis():
"""
Workflow 3: Comprehensive Risk Analysis
Combines Polaroid's data processing with OptimizR's risk metrics
for portfolio risk assessment.
"""
print("\n" + "=" * 70)
print("Workflow 3: Portfolio Risk Analysis")
print("=" * 70)
# Simulate multi-asset portfolio returns
np.random.seed(42)
n_days = 252
n_assets = 3
print("\n1. Simulating 3-asset portfolio (1 year daily data):")
# Generate correlated returns
corr_matrix = np.array([
[1.0, 0.6, 0.3],
[0.6, 1.0, 0.4],
[0.3, 0.4, 1.0]
])
# Cholesky decomposition for correlation
L = np.linalg.cholesky(corr_matrix)
uncorrelated = np.random.randn(n_days, n_assets) * 0.015
returns = uncorrelated @ L.T
# Add drift
returns[:, 0] += 0.0008 # Asset 1: 20% annual
returns[:, 1] += 0.0004 # Asset 2: 10% annual
returns[:, 2] += 0.0006 # Asset 3: 15% annual
print(f" Asset 1: Expected 20% annual return")
print(f" Asset 2: Expected 10% annual return")
print(f" Asset 3: Expected 15% annual return")
# Step 2: Individual asset statistics
print("\n2. Individual Asset Analysis:")
for i in range(n_assets):
mean, std, skew, kurt, sharpe = optimizr.return_statistics_py(
returns[:, i].tolist()
)
print(f"\n Asset {i+1}:")
print(f" Return (annual): {mean*252*100:.1f}%")
print(f" Volatility (annual): {std*np.sqrt(252)*100:.1f}%")
print(f" Skewness: {skew:.3f}")
print(f" Kurtosis: {kurt:.3f}")
print(f" Sharpe ratio: {sharpe:.3f}")
# Step 3: Mean-reversion analysis
print("\n3. Mean-Reversion Analysis:")
prices = [np.cumprod(1 + returns[:, i]) * 100 for i in range(n_assets)]
for i in range(n_assets):
hurst = optimizr.rolling_hurst_exponent_py(
returns[:, i].tolist(),
window_size=60
)
avg_hurst = np.mean(hurst)
half_life = optimizr.rolling_half_life_py(
prices[i].tolist(),
window_size=60
)
# Filter out infinities
finite_hl = [hl for hl in half_life if np.isfinite(hl)]
avg_hl = np.mean(finite_hl) if finite_hl else float('inf')
print(f"\n Asset {i+1}:")
print(f" Hurst exponent: {avg_hurst:.3f}", end="")
if avg_hurst < 0.45:
print(" (mean-reverting)")
elif avg_hurst > 0.55:
print(" (trending)")
else:
print(" (random walk)")
if np.isfinite(avg_hl):
print(f" Half-life: {avg_hl:.1f} days")
# Step 4: Correlation analysis
print("\n4. Correlation Matrix (rolling 60-day):")
for i in range(n_assets):
for j in range(i+1, n_assets):
corr = optimizr.rolling_correlation_py(
returns[:, i].tolist(),
returns[:, j].tolist(),
window_size=60
)
avg_corr = np.mean(corr)
print(f" Asset {i+1} ↔ Asset {j+1}: {avg_corr:.3f}")
# Step 5: Portfolio optimization weights (equal risk contribution)
print("\n5. Portfolio Construction:")
weights = [1/n_assets] * n_assets
portfolio_returns = returns @ np.array(weights)
mean, std, skew, kurt, sharpe = optimizr.return_statistics_py(
portfolio_returns.tolist()
)
print(f" Equal-weight portfolio:")
print(f" Return (annual): {mean*252*100:.1f}%")
print(f" Volatility (annual): {std*np.sqrt(252)*100:.1f}%")
print(f" Sharpe ratio: {sharpe:.3f}")
print("\n✅ Workflow 3 complete! Comprehensive risk analysis finished.")
def workflow4_pairs_trading_pipeline():
"""
Workflow 4: Complete Pairs Trading Pipeline
End-to-end pairs trading: cointegration check, parameter optimization,
and risk management using OptimizR's integrated tools.
"""
print("\n" + "=" * 70)
print("Workflow 4: Pairs Trading Pipeline")
print("=" * 70)
# Generate cointegrated pair
np.random.seed(42)
n_days = 500
# Asset 1: Random walk with drift
returns1 = np.random.normal(0.0003, 0.015, n_days)
prices1 = 100 * np.cumprod(1 + returns1)
# Asset 2: Cointegrated with Asset 1
spread_noise = np.random.normal(0, 0.01, n_days)
prices2 = prices1 * 0.9 + np.cumsum(spread_noise)
# Calculate spread
spread = prices1 - prices2
print("\n1. Cointegration Analysis:")
# Check mean-reversion
spread_returns = np.diff(spread) / spread[:-1]
hurst = optimizr.rolling_hurst_exponent_py(
spread_returns.tolist(),
window_size=60
)
avg_hurst = np.mean(hurst)
print(f" Hurst exponent: {avg_hurst:.3f}", end="")
if avg_hurst < 0.5:
print(" ✅ Mean-reverting (good for pairs trading)")
else:
print(" ⚠️ Not clearly mean-reverting")
# Estimate half-life
half_lives = optimizr.rolling_half_life_py(
spread.tolist(),
window_size=60
)
finite_hl = [hl for hl in half_lives if np.isfinite(hl) and hl > 0]
avg_hl = np.mean(finite_hl) if finite_hl else float('inf')
if np.isfinite(avg_hl):
print(f" Half-life: {avg_hl:.1f} days (reversion speed)")
# Correlation check
returns2 = np.diff(prices2) / prices2[:-1]
corr = optimizr.rolling_correlation_py(
returns1[1:].tolist(),
returns2.tolist(),
window_size=60
)
avg_corr = np.mean(corr)
print(f" Correlation: {avg_corr:.3f}", end="")
if avg_corr > 0.7:
print(" ✅ Strong correlation")
elif avg_corr > 0.5:
print(" ⚠️ Moderate correlation")
else:
print(" ❌ Weak correlation")
# Step 2: Optimize strategy parameters
print("\n2. Strategy Parameter Optimization:")
def pairs_strategy(params: List[float]) -> float:
"""
Pairs trading with mean-reversion.
params = [entry_z, exit_z, stop_loss]
Returns: negative Sharpe (for minimization)
"""
entry_z = params[0]
exit_z = params[1]
stop_loss = params[2]
# Calculate z-score
window = 20
spread_ma = np.convolve(spread, np.ones(window)/window, mode='valid')
spread_std = np.array([
np.std(spread[i:i+window])
for i in range(len(spread) - window + 1)
])
aligned_spread = spread[window-1:]
z_score = (aligned_spread - spread_ma) / (spread_std + 1e-6)
# Trading logic
position = 0 # 1 = long spread, -1 = short spread
returns = []
entry_value = 0
for i in range(1, len(z_score)):
# Entry signals
if z_score[i] > entry_z and position == 0:
position = -1 # Short spread (short asset1, long asset2)
entry_value = aligned_spread[i]
elif z_score[i] < -entry_z and position == 0:
position = 1 # Long spread (long asset1, short asset2)
entry_value = aligned_spread[i]
# Exit signals
if abs(z_score[i]) < exit_z and position != 0:
position = 0
# Stop loss
if position != 0 and entry_value != 0:
pnl = position * (aligned_spread[i] - entry_value) / abs(entry_value)
if pnl < -stop_loss:
position = 0
# Calculate returns
if position != 0:
spread_ret = (aligned_spread[i] - aligned_spread[i-1]) / aligned_spread[i-1]
returns.append(position * spread_ret)
else:
returns.append(0)
if len(returns) < 10:
return 999.0
mean_ret = np.mean(returns)
std_ret = np.std(returns)
if std_ret == 0:
return 999.0
sharpe = mean_ret / std_ret * np.sqrt(252)
return -sharpe
print(" Optimizing: [entry_z, exit_z, stop_loss]")
result = optimizr.differential_evolution(
pairs_strategy,
bounds=[(1.5, 3.0), (0.1, 1.0), (0.02, 0.1)],
strategy="best1",
max_iterations=30,
population_size=15
)
print(f" Optimal parameters:")
print(f" Entry z-score: {result['x'][0]:.2f}")
print(f" Exit z-score: {result['x'][1]:.2f}")
print(f" Stop loss: {result['x'][2]*100:.1f}%")
print(f" Expected Sharpe: {-result['fun']:.3f}")
print("\n✅ Workflow 4 complete! Pairs trading strategy optimized.")
return result
if __name__ == "__main__":
print("=" * 70)
print("Polaroid + OptimizR Integration Examples")
print("=" * 70)
print("\nDemonstrating 4 integrated workflows combining time-series")
print("operations with optimization and statistical inference.")
# Run all workflows
workflow1_regime_detection_with_features()
workflow2_strategy_optimization()
workflow3_risk_analysis()
workflow4_pairs_trading_pipeline()
print("\n" + "=" * 70)
print("✅ All integration workflows completed successfully!")
print("=" * 70)
print("\nThese examples show how to combine:")
print(" • Polaroid's time-series operations (lag, diff, pct_change)")
print(" • OptimizR's optimization (DE, grid search)")
print(" • OptimizR's inference (HMM, MCMC)")
print(" • OptimizR's time-series helpers (Hurst, half-life, etc.)")
print("\nFor production use, connect to Polaroid gRPC for data processing.")
print("=" * 70)