docs/TROUBLESHOOTING.md

# ExPostFacto Troubleshooting Guide

This guide helps you diagnose and fix common issues when using ExPostFacto.

## Table of Contents

- [Installation Issues](#installation-issues)
- [Data Problems](#data-problems)
- [Strategy Development Issues](#strategy-development-issues)
- [Performance Problems](#performance-problems)
- [Validation Errors](#validation-errors)
- [Common Error Messages](#common-error-messages)
- [Debugging Tips](#debugging-tips)

## Installation Issues

### Problem: Mix dependencies won't resolve

**Error:**

```
** (Mix) Could not resolve dependency :ex_post_facto
```

**Solutions:**

1. Check your `mix.exs` dependency specification:

   ```elixir
   {:ex_post_facto, "~> 0.2.0"}
   ```

2. Clear dependency cache:

   ```bash
   mix deps.clean --all
   mix deps.get
   ```

3. Check Elixir/OTP version compatibility:
   ```bash
   elixir --version
   ```

### Problem: Compilation errors

**Error:**

```
** (CompileError) lib/my_strategy.ex:5: undefined function buy/0
```

**Solution:**
Make sure you're using the Strategy behaviour correctly:

```elixir
defmodule MyStrategy do
  use ExPostFacto.Strategy  # This imports buy/0, sell/0, etc.

  def init(_opts), do: {:ok, %{}}
  def next(state), do: {:ok, state}
end
```

## Data Problems

### Problem: "Data cannot be empty" error

**Cause:** Your data list is empty or becomes empty after cleaning.

**Solutions:**

1. Check your data source:

   ```elixir
   IO.inspect(length(market_data), label: "Data length")
   ```

2. Verify data format:

   ```elixir
   IO.inspect(hd(market_data), label: "First data point")
   ```

3. Check if data cleaning removes all points:
   ```elixir
   {:ok, cleaned} = ExPostFacto.clean_data(market_data)
   IO.puts("Original: #{length(market_data)}, Cleaned: #{length(cleaned)}")
   ```

### Problem: CSV file loading fails

**Error:**

```
{:error, "failed to load data: failed to read file: enoent"}
```

**Solutions:**

1. Check file path:

   ```elixir
   File.exists?("path/to/data.csv")
   ```

2. Use absolute paths:

   ```elixir
   path = Path.expand("data/market_data.csv")
   {:ok, result} = ExPostFacto.backtest(path, strategy)
   ```

3. Verify CSV format:
   ```csv
   Date,Open,High,Low,Close,Volume
   2023-01-01,100.0,105.0,98.0,102.0,1000000
   ```

### Problem: Data validation errors

**Error:**

```
{:error, "data point 5: invalid OHLC data: high (95.0) must be >= low (98.0)"}
```

**Solutions:**

1. Clean data before validation:

   ```elixir
   {:ok, clean_data} = ExPostFacto.clean_data(dirty_data)
   {:ok, result} = ExPostFacto.backtest(clean_data, strategy)
   ```

2. Fix data manually:

   ```elixir
   fixed_data = Enum.map(data, fn point ->
     %{point |
       high: max(point.high, max(point.open, point.close)),
       low: min(point.low, min(point.open, point.close))
     }
   end)
   ```

3. Skip validation (not recommended):
   ```elixir
   {:ok, result} = ExPostFacto.backtest(data, strategy, validate_data: false)
   ```

## Strategy Development Issues

### Problem: Strategy never generates trades

**Symptoms:** `trades_count: 0` in results.

**Debugging steps:**

1. Enable debug mode:

   ```elixir
   {:ok, result} = ExPostFacto.backtest(
     data, strategy,
     enhanced_validation: true,
     debug: true
   )
   ```

2. Add logging to your strategy:

   ```elixir
   def next(state) do
     current_price = data().close
     IO.puts("Current price: #{current_price}")

     if current_price > 100 do
       IO.puts("Buy condition met!")
       buy()
     end

     {:ok, state}
   end
   ```

3. Check data availability:
   ```elixir
   def next(state) do
     if length(state.price_history) < 20 do
       IO.puts("Not enough data yet: #{length(state.price_history)}")
     else
       # Your strategy logic
     end
     {:ok, state}
   end
   ```

### Problem: Strategy crashes during execution

**Error:**

```
** (FunctionClauseError) no function clause matching in MyStrategy.next/1
```

**Solutions:**

1. Always return proper tuple from `next/1`:

   ```elixir
   def next(state) do
     # Your logic here
     {:ok, state}  # Always return this format
   end
   ```

2. Handle all possible states:

   ```elixir
   def next(state) do
     case calculate_signal(state) do
       {:ok, :buy} -> buy()
       {:ok, :sell} -> sell()
       {:error, _reason} -> :ok  # Handle errors gracefully
     end

     {:ok, state}
   end
   ```

3. Use pattern matching safely:
   ```elixir
   def next(state) do
     case Map.get(state, :price_history, []) do
       [] -> :ok  # No history yet
       prices when length(prices) >= 10 ->
         # Your logic
       _ -> :ok  # Not enough data
     end

     {:ok, state}
   end
   ```

### Problem: Indicators return nil or unexpected values

**Cause:** Insufficient data for indicator calculation.

**Solutions:**

1. Check data length requirements:

   ```elixir
   def next(state) do
     price_history = [data().close | state.price_history]

     if length(price_history) >= 20 do  # Ensure enough data
       sma = indicator(:sma, price_history, 20)
       current_sma = List.first(sma)

       if current_sma do  # Check for nil
         # Use indicator value
       end
     end

     {:ok, %{state | price_history: price_history}}
   end
   ```

2. Handle edge cases:
   ```elixir
   def safe_indicator(type, data, params) do
     case indicator(type, data, params) do
       [nil | _] -> nil
       [value | _] when is_number(value) -> value
       _ -> nil
     end
   end
   ```

## Performance Problems

### Problem: Backtests are very slow

**Solutions:**

1. Limit price history:

   ```elixir
   def next(state) do
     max_history = 100  # Only keep what you need
     price_history =
       [data().close | state.price_history]
       |> Enum.take(max_history)

     {:ok, %{state | price_history: price_history}}
   end
   ```

2. Use streaming for large datasets:

   ```elixir
   {:ok, result} = ExPostFacto.backtest_stream(
     "large_file.csv",
     strategy,
     chunk_size: 1000
   )
   ```

3. Optimize indicator calculations:

   ```elixir
   # Bad: Recalculate every time
   def next(state) do
     sma = indicator(:sma, state.price_history, 20)
     # ...
   end

   # Good: Cache calculations
   def next(state) do
     state = maybe_update_sma(state)
     # Use cached state.sma_value
   end
   ```

### Problem: Optimization takes too long

**Solutions:**

1. Reduce parameter ranges:

   ```elixir
   # Instead of large ranges
   [fast: 5..50, slow: 20..200]

   # Use smaller, focused ranges
   [fast: 8..12, slow: 18..22]
   ```

2. Use random search for large spaces:

   ```elixir
   {:ok, result} = ExPostFacto.optimize(
     data, strategy,
     param_ranges,
     method: :random_search,
     samples: 100
   )
   ```

3. Set reasonable limits:
   ```elixir
   {:ok, result} = ExPostFacto.optimize(
     data, strategy,
     param_ranges,
     max_combinations: 500
   )
   ```

## Validation Errors

### Problem: Enhanced validation errors

**Error:**

```
{:error, %ExPostFacto.Validation.ValidationError{
  message: "Strategy validation failed",
  context: %{...}
}}
```

**Solutions:**

1. Format errors for readability:

   ```elixir
   case ExPostFacto.backtest(data, strategy, enhanced_validation: true) do
     {:ok, result} -> result
     {:error, %ExPostFacto.Validation.ValidationError{} = error} ->
       IO.puts(ExPostFacto.Validation.format_error(error))
       :error
   end
   ```

2. Use debug mode:
   ```elixir
   {:ok, result} = ExPostFacto.backtest(
     data, strategy,
     enhanced_validation: true,
     debug: true
   )
   ```

## Common Error Messages

### "Module not found" or "Function not exported"

**Error:**

```
** (UndefinedFunctionError) function MyStrategy.init/1 is undefined
```

**Solution:**
Ensure your strategy module implements the required callbacks:

```elixir
defmodule MyStrategy do
  use ExPostFacto.Strategy

  def init(opts) do
    {:ok, %{}}
  end

  def next(state) do
    {:ok, state}
  end
end
```

### "Invalid strategy format"

**Cause:** Incorrect strategy specification.

**Solutions:**

```elixir
# Correct formats:
{MyStrategy, :call, []}           # MFA tuple
{MyStrategy, [param: value]}      # Strategy behaviour

# Incorrect:
MyStrategy                        # Just module name
{MyStrategy}                      # Incomplete tuple
```

### "Position function not available"

**Error:**

```
** (UndefinedFunctionError) function :position not found
```

**Solution:**
Use `position()` inside Strategy behaviour context:

```elixir
defmodule MyStrategy do
  use ExPostFacto.Strategy

  def next(state) do
    current_pos = position()  # This works here
    # ...
  end
end

# Not in MFA functions:
defmodule MyMFAStrategy do
  def call(data, result) do
    # position() not available here
    # Use result.current_position instead
  end
end
```

## Debugging Tips

### Enable Enhanced Logging

```elixir
# Full debugging
{:ok, result} = ExPostFacto.backtest(
  data, strategy,
  enhanced_validation: true,
  debug: true,
  warnings: true
)
```

### Add Temporary Logging

```elixir
def next(state) do
  IO.inspect(data(), label: "Current data")
  IO.inspect(position(), label: "Current position")
  IO.inspect(equity(), label: "Current equity")

  # Your strategy logic
  {:ok, state}
end
```

### Test with Minimal Data

```elixir
# Create simple test data
test_data = [
  %{open: 100, high: 105, low: 98, close: 102},
  %{open: 102, high: 108, low: 101, close: 106},
  %{open: 106, high: 110, low: 104, close: 108}
]

{:ok, result} = ExPostFacto.backtest(test_data, strategy)
```

### Validate Strategy Logic Separately

```elixir
# Test your strategy logic outside of backtesting
defmodule StrategyTester do
  def test_logic do
    state = %{threshold: 100}
    data = %{close: 105}

    # Mock the data() function
    result = if data.close > state.threshold, do: :buy, else: :sell
    IO.puts("Signal: #{result}")
  end
end
```

### Use IEx for Interactive Testing

```elixir
# In IEx
iex> data = [%{open: 100, high: 105, low: 98, close: 102}]
iex> {:ok, result} = ExPostFacto.backtest(data, {MyStrategy, []})
iex> IO.inspect(result.result)
```

### Profile Performance

```elixir
# Time your backtests
{time, {:ok, result}} = :timer.tc(fn ->
  ExPostFacto.backtest(data, strategy)
end)

IO.puts("Backtest took #{time / 1000} ms")
```

### Check Memory Usage

```elixir
# Monitor memory during development
before = :erlang.memory(:total)
{:ok, result} = ExPostFacto.backtest(data, strategy)
after_mem = :erlang.memory(:total)

IO.puts("Memory used: #{(after_mem - before) / 1024 / 1024} MB")
```

## Getting Help

If you're still having issues:

1. **Check the logs** - Look for warning messages that might indicate problems
2. **Review examples** - Compare your code with working examples in `lib/ex_post_facto/example_strategies/`
3. **Read the docs** - Check the API reference and guides in the `docs/` directory
4. **Create minimal reproduction** - Simplify your strategy to isolate the issue
5. **Open an issue** - If you've found a bug, please report it on GitHub

## Common Gotchas

1. **Strategy state must be immutable** - Always return updated state from `next/1`
2. **Indicator data order** - Most recent data should be first in the list
3. **Position management** - `buy()` enters long, `close_buy()` exits long
4. **Data requirements** - Some indicators need minimum data points to work
5. **Memory management** - Limit historical data to what you actually need

Remember: When in doubt, enable debug mode and add logging to understand what's happening in your strategy!

## Performance Checklist

- [ ] Limit price history to reasonable size (< 200 points usually sufficient)
- [ ] Cache expensive calculations when possible
- [ ] Use appropriate optimization methods for your parameter space
- [ ] Consider streaming for very large datasets
- [ ] Profile memory usage for long-running strategies
- [ ] Use concurrent optimization when testing many parameters

Happy debugging! 🐛➡️✨