{"id":21692522,"url":"https://github.com/maxbeizer/ex_post_facto","last_synced_at":"2025-10-28T00:41:04.598Z","repository":{"id":211936465,"uuid":"730330818","full_name":"maxbeizer/ex_post_facto","owner":"maxbeizer","description":"backtesting in elixir","archived":false,"fork":false,"pushed_at":"2024-03-24T11:08:52.000Z","size":223,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T17:56:52.204Z","etag":null,"topics":["elixir","finance"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/ex_post_facto","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxbeizer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-12-11T17:31:48.000Z","updated_at":"2024-03-24T19:00:45.000Z","dependencies_parsed_at":"2023-12-20T16:30:55.395Z","dependency_job_id":"80f71679-a81c-4aae-9304-95d4ae0f8477","html_url":"https://github.com/maxbeizer/ex_post_facto","commit_stats":null,"previous_names":["maxbeizer/ex_post_facto"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbeizer%2Fex_post_facto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbeizer%2Fex_post_facto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbeizer%2Fex_post_facto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbeizer%2Fex_post_facto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxbeizer","download_url":"https://codeload.github.com/maxbeizer/ex_post_facto/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244623100,"owners_count":20483073,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","finance"],"created_at":"2024-11-25T18:16:23.033Z","updated_at":"2025-10-28T00:41:04.581Z","avatar_url":"https://github.com/maxbeizer.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ExPostFacto\n\n**A comprehensive backtesting library for trading strategies written in Elixir.**\n\n\u003e [!IMPORTANT]\n\u003e This library is under active, pre 1.0 development. The APIs are not to be considered stable. Calculations may not be correct. See the [LICENSE](LICENSE) but use at your own risk.\n\nExPostFacto empowers traders and developers to test their trading strategies against historical data with confidence. Built with Elixir's concurrency and fault-tolerance in mind, it provides enterprise-grade backtesting capabilities with an intuitive API.\n\n## 🚀 Why ExPostFacto?\n\n- **🎯 Easy to Use**: Simple API that gets you backtesting in minutes\n- **📊 Professional Grade**: Comprehensive statistics and performance metrics\n- **🔧 Flexible**: Support for simple functions or advanced strategy behaviours\n- **⚡ Fast**: Concurrent optimization and streaming for large datasets\n- **🧹 Robust**: Built-in data validation, cleaning, and error handling\n- **📈 Complete**: 20+ technical indicators and optimization algorithms\n\n## ✨ Key Features\n\n### Multiple Input Formats\n\n- **CSV files** - Load data directly from CSV files\n- **JSON** - Parse JSON market data\n- **Lists of maps** - Use runtime data structures\n- **Streaming** - Handle large datasets efficiently\n\n### Data Validation \u0026 Cleaning\n\n- **Comprehensive OHLCV validation** with detailed error messages\n- **Automatic data cleaning** - Remove invalid points, sort by timestamp\n- **Enhanced timestamp handling** - Support for multiple date formats\n- **Duplicate detection** and removal\n\n### Flexible Strategy Framework\n\n- **Simple MFA functions** for quick prototypes\n- **Advanced Strategy behaviour** with state management\n- **Built-in helper functions** - `buy()`, `sell()`, `position()`, etc.\n- **20+ technical indicators** - SMA, EMA, RSI, MACD, Bollinger Bands, and more\n\n### Performance \u0026 Optimization\n\n- **Parameter optimization** with grid search, random search, walk-forward analysis\n- **Concurrent processing** for large parameter spaces\n- **Memory-efficient streaming** for massive datasets\n- **Performance profiling** and bottleneck identification\n\n### Comprehensive Analytics\n\n- **30+ performance metrics** - Sharpe ratio, CAGR, max drawdown, profit factor\n- **Trade analysis** - Win rate, best/worst trades, trade duration\n- **Risk metrics** - Drawdown analysis, volatility measures\n- **Visual data** - Heatmaps for parameter optimization\n\nSee [ENHANCED_DATA_HANDLING_EXAMPLES.md](docs/ENHANCED_DATA_HANDLING_EXAMPLES.md) for detailed usage examples.\n\n## LiveBook Integration\n\nExPostFacto works seamlessly with [LiveBook](https://livebook.dev/) for interactive backtesting and analysis:\n\n```elixir\n# In LiveBook, install dependencies:\nMix.install([\n  {:ex_post_facto, \"~\u003e 0.2.0\"},\n  {:kino, \"~\u003e 0.12.0\"},\n  {:kino_vega_lite, \"~\u003e 0.1.0\"}\n])\n\n# Run interactive backtests with rich visualizations\n{:ok, result} = ExPostFacto.backtest(data, {MyStrategy, :call, []})\n```\n\nSee [LiveBook Integration Guide](docs/LIVEBOOK_INTEGRATION.md) for comprehensive examples, interactive forms, and visualization techniques.\n\n## 📖 Quick Start\n\n### Installation\n\nAdd ExPostFacto to your `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:ex_post_facto, \"~\u003e 0.2.0\"}\n  ]\nend\n```\n\n### Your First Backtest\n\n```elixir\n# Sample market data\nmarket_data = [\n  %{open: 100.0, high: 105.0, low: 98.0, close: 102.0, timestamp: \"2023-01-01\"},\n  %{open: 102.0, high: 108.0, low: 101.0, close: 106.0, timestamp: \"2023-01-02\"},\n  %{open: 106.0, high: 110.0, low: 104.0, close: 108.0, timestamp: \"2023-01-03\"}\n]\n\n# Simple buy-and-hold strategy\n{:ok, result} = ExPostFacto.backtest(\n  market_data,\n  {ExPostFacto.ExampleStrategies.SimpleBuyHold, []},\n  starting_balance: 10_000.0\n)\n\n# View results\nIO.puts(\"Total return: $#{result.result.total_profit_and_loss}\")\nIO.puts(\"Win rate: #{result.result.win_rate}%\")\n```\n\n### Load Data from CSV\n\n```elixir\n# ExPostFacto automatically handles CSV files\n{:ok, result} = ExPostFacto.backtest(\n  \"path/to/market_data.csv\",\n  {MyStrategy, :call, []},\n  starting_balance: 100_000.0\n)\n```\n\n## 🎯 Strategy Development\n\n### Simple Function Strategy (MFA)\n\n```elixir\ndefmodule SimpleThresholdStrategy do\n  def call(data, _result) do\n    if data.close \u003e 105.0, do: :buy, else: :sell\n  end\nend\n\n{:ok, result} = ExPostFacto.backtest(\n  market_data,\n  {SimpleThresholdStrategy, :call, []},\n  starting_balance: 10_000.0\n)\n```\n\n### Advanced Strategy Behaviour\n\n```elixir\ndefmodule MovingAverageStrategy do\n  use ExPostFacto.Strategy\n\n  def init(opts) do\n    {:ok, %{\n      fast_period: Keyword.get(opts, :fast_period, 10),\n      slow_period: Keyword.get(opts, :slow_period, 20),\n      price_history: []\n    }}\n  end\n\n  def next(state) do\n    current_price = data().close\n    price_history = [current_price | state.price_history]\n\n    if length(price_history) \u003e= state.slow_period do\n      fast_sma = indicator(:sma, price_history, state.fast_period)\n      slow_sma = indicator(:sma, price_history, state.slow_period)\n\n      if List.first(fast_sma) \u003e List.first(slow_sma) do\n        buy()\n      else\n        sell()\n      end\n    end\n\n    {:ok, %{state | price_history: price_history}}\n  end\nend\n\n# Run with custom parameters\n{:ok, result} = ExPostFacto.backtest(\n  market_data,\n  {MovingAverageStrategy, [fast_period: 5, slow_period: 15]},\n  starting_balance: 10_000.0\n)\n```\n\n## 📈 Technical Indicators\n\nExPostFacto includes 20+ built-in technical indicators:\n\n```elixir\n# Available indicators\nprices = [100, 101, 102, 103, 104, 105]\n\nsma_20 = indicator(:sma, prices, 20)\nema_12 = indicator(:ema, prices, 12)\nrsi_14 = indicator(:rsi, prices, 14)\n{macd, signal, histogram} = indicator(:macd, prices)\n{bb_upper, bb_middle, bb_lower} = indicator(:bollinger_bands, prices)\n\n# Crossover detection\nif crossover?(fast_sma, slow_sma) do\n  buy()\nend\n```\n\n## 🎛️ Strategy Optimization\n\nFind optimal parameters automatically:\n\n```elixir\n# Grid search optimization\n{:ok, result} = ExPostFacto.optimize(\n  market_data,\n  MovingAverageStrategy,\n  [fast_period: 5..15, slow_period: 20..30],\n  maximize: :sharpe_ratio\n)\n\nIO.puts(\"Best parameters: #{inspect(result.best_params)}\")\nIO.puts(\"Best Sharpe ratio: #{result.best_score}\")\n\n# Walk-forward analysis for robust testing\n{:ok, result} = ExPostFacto.optimize(\n  market_data,\n  MovingAverageStrategy,\n  [fast_period: 5..15, slow_period: 20..30],\n  method: :walk_forward,\n  training_window: 252,  # 1 year\n  validation_window: 63  # 3 months\n)\n```\n\n## 🧹 Data Validation \u0026 Cleaning\n\nExPostFacto ensures your data is clean and valid:\n\n```elixir\n# Validate data\ncase ExPostFacto.validate_data(market_data) do\n  :ok -\u003e IO.puts(\"Data is valid!\")\n  {:error, reason} -\u003e IO.puts(\"Validation error: #{reason}\")\nend\n\n# Clean messy data automatically\n{:ok, clean_data} = ExPostFacto.clean_data(dirty_data)\n\n# Enhanced error handling\n{:ok, result} = ExPostFacto.backtest(\n  market_data,\n  strategy,\n  enhanced_validation: true,\n  debug: true\n)\n```\n\n## 📊 Example Strategies\n\nExPostFacto includes several example strategies:\n\n```elixir\n# Moving Average Crossover\n{:ok, result} = ExPostFacto.backtest(\n  data,\n  {ExPostFacto.ExampleStrategies.SmaStrategy, [fast_period: 10, slow_period: 20]}\n)\n\n# RSI Mean Reversion\n{:ok, result} = ExPostFacto.backtest(\n  data,\n  {ExPostFacto.ExampleStrategies.RSIMeanReversionStrategy, [\n    rsi_period: 14,\n    oversold_threshold: 30,\n    overbought_threshold: 70\n  ]}\n)\n\n# Bollinger Band Strategy\n{:ok, result} = ExPostFacto.backtest(\n  data,\n  {ExPostFacto.ExampleStrategies.BollingerBandStrategy, [period: 20, std_dev: 2.0]}\n)\n\n# Breakout Strategy\n{:ok, result} = ExPostFacto.backtest(\n  data,\n  {ExPostFacto.ExampleStrategies.BreakoutStrategy, [\n    lookback_period: 20,\n    breakout_threshold: 0.02\n  ]}\n)\n```\n\n## 📚 Documentation \u0026 Learning\n\n### Complete Documentation\n\n- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Step-by-step introduction\n- **[Interactive Tutorial](docs/tutorial.livemd)** - Livebook tutorial with examples\n- **[Strategy API Guide](docs/STRATEGY_API.md)** - Comprehensive strategy development\n- **[Technical Indicators](docs/INDICATORS.md)** - All available indicators and usage\n- **[Best Practices](docs/BEST_PRACTICES.md)** - Guidelines for effective strategies\n- **[Migration Guide](docs/MIGRATION_GUIDE.md)** - Moving from other libraries\n\n### Data Handling\n\n- **[Enhanced Data Handling](docs/ENHANCED_DATA_HANDLING_EXAMPLES.md)** - Data formats and validation\n- **[Error Handling](docs/ENHANCED_ERROR_HANDLING_SUMMARY.md)** - Debugging and validation\n\n### Advanced Features\n\n- **[Optimization Guide](docs/OPTIMIZATION.md)** - Parameter optimization techniques\n- **[Comprehensive Metrics](docs/COMPREHENSIVE_METRICS.md)** - Performance analysis\n\n## 🔧 Advanced Features\n\n### Streaming for Large Datasets\n\n```elixir\n# Handle massive datasets efficiently\n{:ok, result} = ExPostFacto.backtest_stream(\n  \"very_large_dataset.csv\",\n  {MyStrategy, []},\n  chunk_size: 1000,\n  memory_limit_mb: 100\n)\n```\n\n### Concurrent Optimization\n\n```elixir\n# Leverage all CPU cores for optimization\n{:ok, result} = ExPostFacto.optimize(\n  data,\n  MyStrategy,\n  parameter_ranges,\n  method: :random_search,\n  samples: 1000,\n  max_concurrent: System.schedulers_online()\n)\n```\n\n### Heatmap Visualization\n\n```elixir\n# Generate parameter heatmaps\n{:ok, optimization_result} = ExPostFacto.optimize(data, MyStrategy, param_ranges)\n{:ok, heatmap} = ExPostFacto.heatmap(optimization_result, :param1, :param2)\n\n# Use heatmap data for visualization\nIO.inspect(heatmap.scores)  # 2D array of performance scores\n```\n\n## 🆚 Comparison with Other Libraries\n\n| Feature               | ExPostFacto  | backtesting.py | Backtrader | QuantConnect |\n| --------------------- | ------------ | -------------- | ---------- | ------------ |\n| **Language**          | Elixir       | Python         | Python     | C#/Python    |\n| **Concurrency**       | ✅ Native    | ❌             | ❌         | ✅           |\n| **Memory Efficiency** | ✅ Streaming | ❌             | ❌         | ✅           |\n| **Data Validation**   | ✅ Built-in  | ❌             | ❌         | ✅           |\n| **Walk-Forward**      | ✅           | ❌             | ✅         | ✅           |\n| **Easy Setup**        | ✅           | ✅             | ❌         | ❌           |\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [contributing guidelines](CONTRIBUTING.MD) and check out the open issues.\n\n## 📄 License\n\nExPostFacto is released under the MIT License. See LICENSE for details.\n\n## 🙏 Acknowledgments\n\nInspired by Python's backtesting.py and other excellent backtesting libraries. Built with the power and elegance of Elixir.\n\n---\n\n**Ready to backtest your trading strategies? [Get started now!](docs/GETTING_STARTED.md)** 🚀\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbeizer%2Fex_post_facto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxbeizer%2Fex_post_facto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbeizer%2Fex_post_facto/lists"}