StrategyCodeCapture API Reference¶
Overview¶
The StrategyCodeCapture class handles automatic discovery and copying of strategy source code files. It supports three capture modes:
- Entry point detection (default) - Stores only the file containing
run_algorithm()call - Strategy.yaml (explicit configuration) - Stores files listed in YAML
- Import analysis (deprecated) - Automatically discovers all imported local modules
New in v1.x: Default behavior changed to entry point detection for storage efficiency during optimization runs.
Class Definition¶
from rustybt.backtest import StrategyCodeCapture
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='backtests/20251019_143527_123/code'
)
Constructor¶
StrategyCodeCapture.__init__¶
def __init__(
self,
strategy_path: str | Path,
output_dir: str | Path,
mode: str = 'import_analysis'
)
Initialize a new strategy code capture instance.
Parameters:
-
strategy_path(str | Path)
Path to the main strategy file -
output_dir(str | Path)
Directory where captured code will be stored -
mode(str, default:'import_analysis')
Capture mode: 'import_analysis': Automatic via import analysis'strategy_yaml': Explicit via strategy.yaml file
Raises:
FileNotFoundError: Strategy file not foundValueError: Invalid capture modeOSError: Cannot access output directory
Example:
# Import analysis mode (default)
capturer = StrategyCodeCapture(
strategy_path='strategies/my_strategy.py',
output_dir='backtests/20251019_143527_123/code'
)
# Strategy YAML mode
capturer = StrategyCodeCapture(
strategy_path='strategies/my_strategy.py',
output_dir='backtests/20251019_143527_123/code',
mode='strategy_yaml'
)
Data Classes¶
EntryPointDetectionResult¶
@dataclass
class EntryPointDetectionResult:
detected_file: Path | None
detection_method: str
confidence: str
warnings: list[str]
Result from entry point detection analysis.
Attributes:
-
detected_file(Path | None)
Path to detected entry point file, orNoneif detection failed -
detection_method(str)
Method used for detection: 'inspect_stack': Runtime stack introspection (most reliable)'ipython': Jupyter notebook detection'frozen': PyInstaller/frozen app detection-
'fallback': Best-effort detection -
confidence(str)
Confidence level:'high','medium','low' -
warnings(list[str])
List of warnings encountered during detection
Example:
from rustybt.backtest.code_capture import StrategyCodeCapture
capturer = StrategyCodeCapture()
result = capturer.detect_entry_point()
if result.detected_file:
print(f"Entry point: {result.detected_file}")
print(f"Method: {result.detection_method}")
print(f"Confidence: {result.confidence}")
else:
print("Detection failed")
print(f"Warnings: {result.warnings}")
Methods¶
detect_entry_point¶
Detect the entry point file containing the run_algorithm() call using runtime introspection.
This method uses inspect.stack() to analyze the call stack and identify the file from which run_algorithm() was invoked. This is the core new feature in v1.x that enables storage-efficient code capture during optimization runs.
Returns: EntryPointDetectionResult with detection details
Raises: Does not raise exceptions - failures are captured in result warnings
Detection Methods:
- Standard Python script (
inspect_stack): Analyzes stack frames to findrun_algorithm()caller - Jupyter notebook (
ipython): Uses IPython metadata when available - Frozen application (
frozen): Detects PyInstaller/cx_Freeze packaged apps - Interactive REPL (
fallback): Best-effort detection for interactive sessions
Example:
from rustybt.backtest.code_capture import StrategyCodeCapture
capturer = StrategyCodeCapture()
# Detect entry point
result = capturer.detect_entry_point()
if result.detected_file:
print(f"✓ Entry point detected: {result.detected_file}")
print(f" Method: {result.detection_method}")
print(f" Confidence: {result.confidence}")
if result.warnings:
print(f" Warnings: {len(result.warnings)}")
for warning in result.warnings:
print(f" - {warning}")
else:
print("✗ Entry point detection failed")
print(" Create strategy.yaml to explicitly specify files")
for warning in result.warnings:
print(f" - {warning}")
Usage in different contexts:
# Standard script execution (best case)
# Running: python my_strategy.py
result = capturer.detect_entry_point()
# → detected_file: Path('my_strategy.py')
# → detection_method: 'inspect_stack'
# → confidence: 'high'
# Jupyter notebook
# Running from notebook cell
result = capturer.detect_entry_point()
# → detected_file: Path('Untitled.ipynb') or Path('<notebook>.ipynb')
# → detection_method: 'ipython'
# → confidence: 'medium'
# Frozen application (PyInstaller)
result = capturer.detect_entry_point()
# → detected_file: Path('my_strategy.py') (from bundled source)
# → detection_method: 'frozen'
# → confidence: 'medium'
# Interactive REPL (lowest confidence)
# Running: python -i
result = capturer.detect_entry_point()
# → detected_file: Path('<stdin>') or None
# → detection_method: 'fallback'
# → confidence: 'low'
# → warnings: ['Interactive session detected - use strategy.yaml for reliability']
Methods¶
capture¶
Execute code capture based on configured mode.
Parameters:
strategy_yaml_path(str | Path | None, default:None)
Optional path to strategy.yaml. If provided, overrides auto-detection.
Returns: List of captured file paths (relative to output_dir)
Raises:
FileNotFoundError: Required files not foundValueError: Invalid configurationOSError: Cannot copy files
Example:
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='backtests/20251019_143527_123/code'
)
# Capture with auto-detected strategy.yaml
captured_files = capturer.capture()
print(captured_files)
# ['my_strategy.py', 'utils/indicators.py', 'utils/risk.py']
# Capture with explicit strategy.yaml path
captured_files = capturer.capture(strategy_yaml_path='custom_strategy.yaml')
analyze_imports¶
Analyze Python import statements to discover strategy files.
Uses Python's Abstract Syntax Tree (AST) to parse import statements and resolve module paths.
Returns: List of discovered file paths
Raises:
SyntaxError: Invalid Python syntax in strategy fileImportError: Cannot resolve import paths
Example:
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='output/code'
)
# Discover files via import analysis
files = capturer.analyze_imports()
print(files)
# [Path('my_strategy.py'), Path('utils/indicators.py'), Path('utils/risk.py')]
load_strategy_yaml¶
Load file list from strategy.yaml configuration.
Parameters:
yaml_path(str | Path | None, default:None)
Path to strategy.yaml. IfNone, looks in strategy directory.
Returns: List of file paths from YAML configuration
Raises:
FileNotFoundError: strategy.yaml not foundyaml.YAMLError: Invalid YAML formatValueError: Invalid YAML structure
Example:
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='output/code',
mode='strategy_yaml'
)
# Load from default location (same directory as strategy)
files = capturer.load_strategy_yaml()
# Load from custom location
files = capturer.load_strategy_yaml(yaml_path='config/strategy.yaml')
print(files)
# [Path('my_strategy.py'), Path('config/params.json'), Path('data/ref.csv')]
copy_files¶
Copy specified files to output directory, preserving structure.
Parameters:
files(list[Path])
List of file paths to copy
Returns: List of copied file paths (relative to output_dir)
Raises:
FileNotFoundError: Source file not found (warns, doesn't fail)OSError: Cannot copy file (warns, doesn't fail)
Example:
from pathlib import Path
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='output/code'
)
files_to_copy = [
Path('my_strategy.py'),
Path('utils/indicators.py'),
Path('config/params.json')
]
copied = capturer.copy_files(files_to_copy)
print(copied)
# ['my_strategy.py', 'utils/indicators.py', 'config/params.json']
# Directory structure preserved:
# output/code/
# ├── my_strategy.py
# ├── utils/
# │ └── indicators.py
# └── config/
# └── params.json
is_framework_module¶
Check if module is part of RustyBT framework.
Parameters:
module_name(str)
Module name to check
Returns: True if framework module, False otherwise
Example:
from rustybt.backtest import StrategyCodeCapture
# Framework modules
print(StrategyCodeCapture.is_framework_module('rustybt.algorithm')) # True
print(StrategyCodeCapture.is_framework_module('rustybt.data')) # True
# Non-framework modules
print(StrategyCodeCapture.is_framework_module('numpy')) # False
print(StrategyCodeCapture.is_framework_module('my_strategy')) # False
is_stdlib_module¶
Check if module is part of Python standard library.
Parameters:
module_name(str)
Module name to check
Returns: True if stdlib module, False otherwise
Example:
from rustybt.backtest import StrategyCodeCapture
# Standard library modules
print(StrategyCodeCapture.is_stdlib_module('os')) # True
print(StrategyCodeCapture.is_stdlib_module('sys')) # True
print(StrategyCodeCapture.is_stdlib_module('datetime')) # True
# Non-stdlib modules
print(StrategyCodeCapture.is_stdlib_module('numpy')) # False
print(StrategyCodeCapture.is_stdlib_module('pandas')) # False
resolve_module_path¶
Resolve module name to file system path.
Parameters:
-
module_name(str)
Module name (e.g.,'utils.indicators') -
base_path(Path)
Base directory for resolution
Returns: Resolved file path or None if not found
Example:
from pathlib import Path
from rustybt.backtest import StrategyCodeCapture
base = Path('/path/to/project')
# Resolve module to file
path = StrategyCodeCapture.resolve_module_path('utils.indicators', base)
print(path) # Path('/path/to/project/utils/indicators.py')
# Module not found
path = StrategyCodeCapture.resolve_module_path('nonexistent', base)
print(path) # None
Usage Examples¶
Example 1: Basic Import Analysis¶
from rustybt.backtest import StrategyCodeCapture
from pathlib import Path
# Create capturer
capturer = StrategyCodeCapture(
strategy_path='strategies/momentum.py',
output_dir='backtests/20251019_143527_123/code'
)
# Capture strategy code
captured_files = capturer.capture()
print(f"Captured {len(captured_files)} files:")
for file in captured_files:
print(f" - {file}")
# Output:
# Captured 3 files:
# - strategies/momentum.py
# - indicators/technical.py
# - risk/manager.py
Example 2: Strategy YAML Configuration¶
strategy.yaml:
Python code:
from rustybt.backtest import StrategyCodeCapture
# Create capturer in YAML mode
capturer = StrategyCodeCapture(
strategy_path='main_strategy.py',
output_dir='backtests/20251019_143527_123/code',
mode='strategy_yaml'
)
# Capture using strategy.yaml
captured_files = capturer.capture()
print("Captured files from strategy.yaml:")
for file in captured_files:
print(f" - {file}")
# Output:
# Captured files from strategy.yaml:
# - main_strategy.py
# - indicators/custom.py
# - config/params.json
# - data/reference.csv
Example 3: Custom Import Analysis¶
from rustybt.backtest import StrategyCodeCapture
from pathlib import Path
# Create capturer
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='output/code'
)
# Step 1: Analyze imports
discovered_files = capturer.analyze_imports()
print(f"Discovered {len(discovered_files)} files via import analysis")
# Step 2: Filter files (custom logic)
filtered_files = [
f for f in discovered_files
if not f.name.startswith('test_')
]
# Step 3: Copy filtered files
copied_files = capturer.copy_files(filtered_files)
print(f"Copied {len(copied_files)} files (excluded test files)")
Example 4: Graceful Error Handling¶
from rustybt.backtest import StrategyCodeCapture
import logging
# Enable logging to see warnings
logging.basicConfig(level=logging.WARNING)
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir='output/code'
)
# Capture will warn about missing files but continue
captured_files = capturer.capture()
# Even if some files are missing, successful captures are returned
print(f"Successfully captured {len(captured_files)} files")
# Check what was captured
for file in captured_files:
print(f" ✓ {file}")
Example 5: Integration with BacktestArtifactManager¶
from rustybt.backtest import BacktestArtifactManager, StrategyCodeCapture
# Create artifact manager
manager = BacktestArtifactManager()
manager.initialize()
# Create code capturer
capturer = StrategyCodeCapture(
strategy_path='my_strategy.py',
output_dir=manager.code_dir
)
# Capture strategy code
captured_files = capturer.capture()
# Update metadata with captured files
metadata = {
'backtest_id': manager.backtest_id,
'captured_files': captured_files,
'strategy_entry_point': str(Path('my_strategy.py').resolve())
}
manager.save_metadata(metadata)
print(f"Captured {len(captured_files)} files to {manager.code_dir}")
Import Analysis Details¶
Supported Import Patterns¶
The import analyzer supports these Python import patterns:
# Absolute imports
import utils.indicators
from utils.indicators import calculate_rsi
# Relative imports
from . import indicators
from .utils import indicators
from ..shared import helpers
# Aliased imports
import utils.indicators as ind
from utils import indicators as ind
# Multiple imports
from utils.indicators import (
calculate_rsi,
calculate_macd,
calculate_bollinger
)
Module Resolution¶
The analyzer resolves modules in this order:
- Relative to strategy directory - Check for local modules
- Python path - Use
sys.pathfor resolution importlib.util.find_spec()- Standard library resolution
Filtering Logic¶
Modules are filtered to exclude:
- Framework modules:
rustybt.* - Standard library:
os,sys,datetime, etc. - Third-party packages:
numpy,pandas,ccxt, etc.
Only local/user modules are captured.
Strategy YAML Format¶
Basic Format¶
# Required: list of files to capture
files:
- my_strategy.py
- utils/indicators.py
- config/params.json
# Optional: metadata (not used by system)
metadata:
name: "My Strategy"
version: "1.0.0"
author: "Quant Team"
Path Resolution¶
Paths in strategy.yaml are resolved relative to the YAML file location:
# If strategy.yaml is in /project/strategies/
files:
- main.py # → /project/strategies/main.py
- utils/helpers.py # → /project/strategies/utils/helpers.py
- ../config/params.py # → /project/config/params.py
Supported File Types¶
Any file type can be specified:
files:
# Python files
- strategy.py
- indicators.py
# Configuration files
- config.json
- params.yaml
- settings.toml
# Data files
- reference_data.csv
- universe.parquet
# Documentation
- README.md
- CHANGELOG.md
Performance Considerations¶
Import Analysis Performance¶
| Project Size | Files | Analysis Time |
|---|---|---|
| Small | 1-5 | < 50ms |
| Medium | 10-20 | < 200ms |
| Large | 50+ | < 1s |
| Very Large | 200+ | < 3s |
Optimization Tips¶
-
Use strategy.yaml for large projects
-
Avoid deep import chains
-
Cache analysis results (if running multiple times)
Thread Safety¶
StrategyCodeCapture instances are not thread-safe. Create separate instances for concurrent operations:
import threading
from rustybt.backtest import StrategyCodeCapture
def capture_strategy(strategy_path, output_dir):
# Each thread gets its own instance
capturer = StrategyCodeCapture(strategy_path, output_dir)
return capturer.capture()
# Safe: separate instances per thread
threads = []
for i in range(5):
t = threading.Thread(
target=capture_strategy,
args=(f'strategy_{i}.py', f'output_{i}/code')
)
threads.append(t)
t.start()
Error Handling¶
The code capturer uses graceful error handling:
- Missing files: Warns but continues
- Permission errors: Warns but continues
- Invalid imports: Logs warning and skips
- Syntax errors: Logs error and skips file
This ensures backtest execution continues even if code capture has issues.
Example logging output:
WARNING: Could not find imported module: advanced.experimental.indicators
WARNING: Permission denied copying: /restricted/secret_code.py
ERROR: Syntax error parsing: broken_strategy.py (backtest continues)
INFO: Successfully captured 8 of 10 discovered files