ExPostFacto API Reference
View SourceThis document provides a comprehensive reference for all ExPostFacto modules, functions, and types.
Core Modules
ExPostFacto
The main module providing the primary backtesting functions.
Main Functions
backtest/3
@spec backtest(
data :: [map()] | String.t(),
strategy :: strategy(),
options :: keyword()
) :: {:ok, Output.t()} | {:error, String.t()}Run a backtest with the given data and strategy.
Parameters:
data- Market data as list of maps, CSV file path, or JSON stringstrategy- Either{Module, :function, args}or{Module, opts}for Strategy behaviouroptions- Keyword list of options
Options:
:starting_balance- Initial capital (default: 10,000.0):validate_data- Enable data validation (default: true):clean_data- Enable data cleaning (default: true):enhanced_validation- Use enhanced validation system (default: false):debug- Enable debug logging (default: false)
Examples:
# Basic backtest
{:ok, result} = ExPostFacto.backtest(data, {MyStrategy, :call, []})
# With options
{:ok, result} = ExPostFacto.backtest(
data,
{MyStrategy, [param: 10]},
starting_balance: 100_000.0,
enhanced_validation: true
)backtest!/3
Same as backtest/3 but raises ExPostFacto.BacktestError on failure.
optimize/4
@spec optimize(
data :: [map()],
strategy_module :: atom(),
param_ranges :: [{atom(), Range.t() | [any()]}],
opts :: keyword()
) :: {:ok, map()} | {:error, String.t()}Optimize strategy parameters using various methods.
Parameters:
data- Market data for optimizationstrategy_module- Strategy module implementing ExPostFacto.Strategyparam_ranges- Parameter names and their ranges/values to testopts- Optimization options
Options:
:method-:grid_search,:random_search, or:walk_forward(default::grid_search):maximize- Metric to optimize (default::sharpe_ratio):samples- Number of samples for random search (default: 100):max_combinations- Maximum combinations for grid search (default: 1000)
Available Metrics:
:sharpe_ratio- Risk-adjusted return:total_return_pct- Total percentage return:cagr_pct- Compound Annual Growth Rate:profit_factor- Gross profit / gross loss:win_rate- Percentage of winning trades:max_draw_down_percentage- Maximum drawdown (minimized)
Examples:
# Grid search optimization
{:ok, result} = ExPostFacto.optimize(
data,
MyStrategy,
[fast: 5..20, slow: 20..50],
maximize: :sharpe_ratio
)
# Random search
{:ok, result} = ExPostFacto.optimize(
data,
MyStrategy,
[fast: 5..20, slow: 20..50],
method: :random_search,
samples: 200
)backtest_stream/3
@spec backtest_stream(
data_source :: String.t() | Enumerable.t(),
strategy :: strategy(),
options :: keyword()
) :: {:ok, Output.t()} | {:error, String.t()}Memory-efficient backtesting for large datasets using streaming.
Options:
:chunk_size- Data points per chunk (default: 1000):window_size- Rolling window for strategy context (default: 100):overlap- Overlap between chunks (default: 10):memory_limit_mb- Memory limit in MB (default: 100)
ExPostFacto.Strategy
Behaviour module for implementing advanced trading strategies.
Callbacks
init/1
@callback init(opts :: keyword()) :: {:ok, state :: any()} | {:error, reason :: any()}Initialize strategy with given options. Return initial state.
next/1
@callback next(state :: any()) :: {:ok, new_state :: any()} | {:error, reason :: any()}Process next data point. Called for each market data point.
Helper Functions
Available when using use ExPostFacto.Strategy:
Trading Actions:
buy/0- Enter long positionsell/0- Enter short positionclose_buy/0- Close long positionclose_sell/0- Close short position
Context Access:
data/0- Get current market data pointequity/0- Get current account equityposition/0- Get current position (:none,:long,:short)
Technical Analysis:
indicator/2- Calculate indicator:indicator(:sma, prices)indicator/3- Calculate with params:indicator(:sma, prices, 20)crossover?/2- Check if series A crosses above series Bcrossunder?/2- Check if series A crosses below series B
Example Strategy
defmodule MySMAStrategy do
use ExPostFacto.Strategy
def init(opts) do
fast = Keyword.get(opts, :fast, 10)
slow = Keyword.get(opts, :slow, 20)
{:ok, %{fast: fast, slow: slow, prices: []}}
end
def next(state) do
price = data().close
prices = [price | state.prices]
if length(prices) >= state.slow do
fast_sma = indicator(:sma, prices, state.fast) |> List.first()
slow_sma = indicator(:sma, prices, state.slow) |> List.first()
if fast_sma > slow_sma and position() != :long do
buy()
elsif fast_sma < slow_sma and position() != :short do
sell()
end
end
{:ok, %{state | prices: prices}}
end
endExPostFacto.Indicators
Technical indicator calculations.
Available Indicators
Moving Averages:
sma/2- Simple Moving Averageema/2- Exponential Moving Averagewma/2- Weighted Moving Average
Momentum Oscillators:
rsi/2- Relative Strength Indexstochastic/2- Stochastic Oscillatorwilliams_r/2- Williams %R
Trend Indicators:
macd/2- Moving Average Convergence Divergenceadx/2- Average Directional Indexaroon/2- Aroon Indicator
Volatility Indicators:
bollinger_bands/2- Bollinger Bandsatr/2- Average True Rangekeltner_channels/2- Keltner Channels
Volume Indicators:
obv/2- On-Balance Volumead_line/2- Accumulation/Distribution Line
Usage
# Within a strategy
sma_20 = indicator(:sma, price_history, 20)
rsi_14 = indicator(:rsi, price_history, 14)
{macd, signal, histogram} = indicator(:macd, price_history)
{upper, middle, lower} = indicator(:bollinger_bands, price_history, {20, 2.0})
# Direct usage
alias ExPostFacto.Indicators
sma_values = Indicators.sma(prices, 20)ExPostFacto.Result
Contains backtesting results and performance statistics.
Key Fields
Trade Statistics:
trades_count- Total number of tradeswins_count- Number of winning tradeswin_rate- Percentage of winning tradestotal_profit_and_loss- Total P&L in currency unitstotal_return_percentage- Total return as percentage
Trade Analysis:
best_trade_percentage- Best single trade returnworst_trade_percentage- Worst single trade returnaverage_trade_percentage- Average trade returntrade_pairs- List of individual trades
Risk Metrics:
max_draw_down_percentage- Maximum drawdownaverage_draw_down_percentage- Average drawdownmax_draw_down_duration- Longest drawdown periodsharpe_ratio- Risk-adjusted return metriccagr_percentage- Compound Annual Growth Rate
Time Analysis:
start_date- Backtest start dateend_date- Backtest end datetotal_days- Total days in backtestmax_trade_duration- Longest trade durationaverage_trade_duration- Average trade duration
ExPostFacto.Optimizer
Parameter optimization functionality.
Methods
grid_search/4
Test all combinations of parameters within specified ranges.
random_search/4
Randomly sample parameter combinations for faster optimization.
walk_forward/4
Rolling window optimization for more robust parameter selection.
Example
# Grid search
{:ok, result} = ExPostFacto.Optimizer.grid_search(
data,
MyStrategy,
[fast: 5..15, slow: 20..30],
maximize: :sharpe_ratio
)
# Access results
best_params = result.best_params # %{fast: 10, slow: 25}
best_score = result.best_score # 1.25
all_results = result.all_results # All parameter combinations testedExPostFacto.Validation
Enhanced data validation and error handling.
Functions
validate_data/2
Comprehensive OHLCV data validation with detailed error messages.
validate_strategy/2
Validate strategy module and parameters.
format_error/1
Format validation errors for user-friendly display.
Usage
# Enhanced validation
case ExPostFacto.backtest(
data,
strategy,
enhanced_validation: true,
debug: true
) do
{:ok, result} -> result
{:error, %ExPostFacto.Validation.ValidationError{} = error} ->
IO.puts(ExPostFacto.Validation.format_error(error))
endData Structures
Market Data Format
Market data should be provided as maps with OHLCV fields:
%{
open: 100.0, # Opening price (required)
high: 105.0, # High price (required)
low: 98.0, # Low price (required)
close: 102.0, # Closing price (required)
volume: 1_000_000, # Volume (optional)
timestamp: "2023-01-01" # Timestamp (optional but recommended)
}Alternative field names:
o,h,l,cinstead ofopen,high,low,closetinstead oftimestamp
CSV Format
Supported CSV formats:
Date,Open,High,Low,Close,Volume
2023-01-01,100.0,105.0,98.0,102.0,1000000
2023-01-02,102.0,108.0,101.0,106.0,1200000Headers are case-insensitive. Common variations supported:
Strategy Types
MFA Tuple:
{Module, :function, args}Strategy Behaviour:
{Module, opts} # where Module implements ExPostFacto.StrategyActions
Valid trading actions returned by strategies:
:buy- Enter long position:sell- Enter short position:close_buy- Close long position:close_sell- Close short position
Error Handling
Exception Types
ExPostFacto.BacktestError- General backtest errorsExPostFacto.DataValidationError- Data validation errors (legacy)ExPostFacto.Validation.ValidationError- Enhanced validation errorsExPostFacto.Validation.StrategyError- Strategy-specific errors
Best Practices
Always use enhanced validation for development:
{:ok, result} = ExPostFacto.backtest( data, strategy, enhanced_validation: true, debug: true )Validate data before backtesting:
case ExPostFacto.validate_data(data) do :ok -> run_backtest(data) {:error, reason} -> handle_error(reason) endHandle errors gracefully:
case ExPostFacto.backtest(data, strategy) do {:ok, result} -> process_result(result) {:error, error} -> Logger.error("Backtest failed: #{error}") :error end
Performance Considerations
Memory Usage
- Use
backtest_stream/3for large datasets - Limit price history in strategies to what's needed
- Enable data cleaning to remove invalid points
Optimization
- Use
:random_searchfor large parameter spaces - Set reasonable
:max_combinationslimits - Consider
:walk_forwardfor robust optimization
Concurrency
- Optimization automatically uses available CPU cores
- Set
:max_concurrentto control parallelism - Streaming processes data in chunks to avoid memory issues
Examples
See the lib/ex_post_facto/example_strategies/ directory for complete strategy examples and the docs/ directory for comprehensive guides and tutorials.