# `ZenQuant.Sizing`
[🔗](https://github.com/ZenHive/zen_quant/blob/v0.8.1/lib/zen_quant/sizing.ex#L1)

Position sizing calculations for trading systems.

Pure functions for calculating position sizes and their compounding growth
based on risk parameters, account size, and volatility.

## Example

    # Fixed fractional sizing (risk 1% of account)
    ZenQuant.Sizing.fixed_fractional(100_000, 0.01, 500)
    # => 2.0  (2 units where each unit has $500 max loss)

    # Kelly criterion
    ZenQuant.Sizing.kelly(0.55, 1.5)
    # => 0.183  (18.3% of bankroll)

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `optimal_f` | 1 | Calculate Ralph Vince's optimal fixed fraction from trade history. | `trades: value` |
| `terminal_wealth_relative` | 2 | Evaluate observed geometric-mean holding-period returns at a fraction. | `trades: value`, `fraction: value` |
| `anti_martingale` | 4 | Calculate anti-martingale position adjustment. | `base_size: value`, `consecutive_wins: value` |
| `volatility_scaled` | 4 | Calculate position size scaled by volatility. | `account_size: value`, `risk_percent: value`, `current_volatility: value`, `target_volatility: value` |
| `simulate_kelly` | 1 | Simulate seeded terminal wealth and drawdown for discrete Kelly bets. | `inputs: value` |
| `median_terminal_wealth_multiple` | 4 | Project the median terminal-wealth multiple after a number of bets. | `win_rate: value`, `win_loss_ratio: value`, `fraction: value`, `bet_count: value` |
| `growth_curve` | 4 | Sample expected log-growth over a caller-supplied fraction range. | `win_rate: value`, `win_loss_ratio: value`, `fraction_range: value`, `step: value` |
| `log_growth` | 3 | Calculate expected logarithmic bankroll growth per bet. | `win_rate: value`, `win_loss_ratio: value`, `fraction: value` |
| `kelly` | 3 | Calculate optimal position size using Kelly criterion. | `win_rate: value`, `win_loss_ratio: value` |
| `max_loss` | 2 | Calculate position size based on maximum loss amount. | `max_loss_amount: value`, `stop_distance: value` |
| `fixed_fractional` | 3 | Calculate position size using fixed fractional method. | `account_size: value`, `risk_percent: value`, `stop_distance: value` |

# `fraction`

```elixir
@type fraction() :: float()
```

Fraction of bankroll (0.0 to 1.0)

# `kelly_path`

```elixir
@type kelly_path() :: %{
  terminal_wealth_multiple: float(),
  max_drawdown: float(),
  log_growth_per_bet: float()
}
```

One simulated path's terminal wealth and maximum peak-to-trough drawdown

# `kelly_simulation_error`

```elixir
@type kelly_simulation_error() ::
  :invalid_inputs
  | :invalid_win_rate
  | :invalid_win_loss_ratio
  | :invalid_fraction
  | :invalid_bet_count
  | :invalid_path_count
  | :invalid_seed
  | :invalid_quantiles
  | :invalid_thresholds
```

Named input error returned by simulate_kelly/1

# `kelly_simulation_inputs`

```elixir
@type kelly_simulation_inputs() :: %{
  win_rate: fraction(),
  win_loss_ratio: float(),
  fraction: fraction(),
  bet_count: pos_integer(),
  path_count: pos_integer(),
  seed: integer(),
  quantiles: [number()],
  thresholds: [number()]
}
```

Caller-supplied inputs for a seeded discrete-Kelly simulation

# `position_size`

```elixir
@type position_size() :: float()
```

Position size in units or currency

# `anti_martingale`

```elixir
@spec anti_martingale(number(), integer(), fraction(), float()) :: position_size()
```

Calculate anti-martingale position adjustment.

## Parameters

  * `base_size` - Starting position size (value)
  * `consecutive_wins` - Number of consecutive wins (negative for losses) (value)

## Options

  * `scale_factor` - Adjustment per win/loss (0.25 = 25%) (default: `0.25`)
  * `max_scale` - Maximum multiplier cap (default: `2.0`)

## Returns

Adjusted position size (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  opts: %{
    scale_factor: %{
      default: 0.25,
      type: :float,
      description: "Adjustment per win/loss (0.25 = 25%)"
    },
    max_scale: %{
      default: 2.0,
      type: :float,
      description: "Maximum multiplier cap"
    }
  },
  params: %{
    base_size: %{description: "Starting position size", kind: :value},
    consecutive_wins: %{
      description: "Number of consecutive wins (negative for losses)",
      kind: :value
    }
  },
  returns: %{type: :float, description: "Adjusted position size"},
  returns_example: 0.1095
}
```

# `fixed_fractional`

```elixir
@spec fixed_fractional(number(), fraction(), number()) :: position_size()
```

Calculate position size using fixed fractional method.

## Parameters

  * `account_size` - Total account equity (value)
  * `risk_percent` - Risk per trade as decimal (e.g., 0.01 = 1%) (value)
  * `stop_distance` - Distance to stop loss in account currency per unit (value)

## Returns

Position size in units (e.g., contracts) (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    account_size: %{description: "Total account equity", kind: :value},
    risk_percent: %{
      description: "Risk per trade as decimal (e.g., 0.01 = 1%)",
      kind: :value
    },
    stop_distance: %{
      description: "Distance to stop loss in account currency per unit",
      kind: :value
    }
  },
  returns: %{
    type: :float,
    description: "Position size in units (e.g., contracts)"
  },
  returns_example: 0.1095
}
```

# `growth_curve`

```elixir
@spec growth_curve(fraction(), float(), {fraction(), fraction()}, float()) ::
  [{fraction(), float()}]
  | {:error, :invalid_fraction | :invalid_fraction_range | :invalid_step}
```

Sample expected log-growth over a caller-supplied fraction range.

## Parameters

  * `win_rate` - Probability of winning (0.0 to 1.0) (value)
  * `win_loss_ratio` - Average win divided by average loss (value)
  * `fraction_range` - Inclusive \{first_fraction, last_fraction\} range (value)
  * `step` - Positive interval between sampled fractions (value)

## Returns

Inclusive [\{fraction, expected_log_growth\}] samples or an error tuple (`list`)

### Example

```elixir
[{0.1, 0.015}, {0.2, 0.0201}]
```

## Errors

  * `:invalid_fraction`
  * `:invalid_fraction_range`
  * `:invalid_step`

```elixir
# descripex:contract
%{
  params: %{
    step: %{
      description: "Positive interval between sampled fractions",
      kind: :value
    },
    win_rate: %{
      description: "Probability of winning (0.0 to 1.0)",
      kind: :value
    },
    win_loss_ratio: %{
      description: "Average win divided by average loss",
      kind: :value
    },
    fraction_range: %{
      description: "Inclusive {first_fraction, last_fraction} range",
      kind: :value
    }
  },
  errors: [:invalid_fraction, :invalid_fraction_range, :invalid_step],
  returns: %{
    type: :list,
    description: "Inclusive [{fraction, expected_log_growth}] samples or an error tuple"
  },
  returns_example: [{0.1, 0.015}, {0.2, 0.0201}]
}
```

# `kelly`

```elixir
@spec kelly(fraction(), float(), fraction()) :: fraction()
```

Calculate optimal position size using Kelly criterion.

## Parameters

  * `win_rate` - Probability of winning (0.0 to 1.0) (value)
  * `win_loss_ratio` - Average win divided by average loss (value)

## Options

  * `kelly_fraction` - Fraction of Kelly to use (0.5 = half Kelly) (default: `0.5`)

## Returns

Optimal bet size as fraction of bankroll, 0.0 if negative EV (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  opts: %{
    kelly_fraction: %{
      default: 0.5,
      type: :float,
      description: "Fraction of Kelly to use (0.5 = half Kelly)"
    }
  },
  params: %{
    win_rate: %{
      description: "Probability of winning (0.0 to 1.0)",
      kind: :value
    },
    win_loss_ratio: %{
      description: "Average win divided by average loss",
      kind: :value
    }
  },
  returns: %{
    type: :float,
    description: "Optimal bet size as fraction of bankroll, 0.0 if negative EV"
  },
  returns_example: 0.1095
}
```

# `log_growth`

```elixir
@spec log_growth(fraction(), float(), fraction()) ::
  float() | {:error, :invalid_fraction}
```

Calculate expected logarithmic bankroll growth per bet.

## Parameters

  * `win_rate` - Probability of winning (0.0 to 1.0) (value)
  * `win_loss_ratio` - Average win divided by average loss (value)
  * `fraction` - Fraction of bankroll bet, greater than 0.0 and less than 1.0 (value)

## Returns

Expected natural-log growth per bet or \{:error, :invalid_fraction\} (`float`)

### Example

```elixir
0.0201
```

## Errors

  * `:invalid_fraction`

```elixir
# descripex:contract
%{
  params: %{
    fraction: %{
      description: "Fraction of bankroll bet, greater than 0.0 and less than 1.0",
      kind: :value
    },
    win_rate: %{
      description: "Probability of winning (0.0 to 1.0)",
      kind: :value
    },
    win_loss_ratio: %{
      description: "Average win divided by average loss",
      kind: :value
    }
  },
  errors: [:invalid_fraction],
  returns: %{
    type: :float,
    description: "Expected natural-log growth per bet or {:error, :invalid_fraction}"
  },
  returns_example: 0.0201
}
```

# `max_loss`

```elixir
@spec max_loss(number(), number()) :: position_size()
```

Calculate position size based on maximum loss amount.

## Parameters

  * `max_loss_amount` - Maximum acceptable loss in account currency (value)
  * `stop_distance` - Distance to stop loss per unit (value)

## Returns

Position size in units (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    stop_distance: %{
      description: "Distance to stop loss per unit",
      kind: :value
    },
    max_loss_amount: %{
      description: "Maximum acceptable loss in account currency",
      kind: :value
    }
  },
  returns: %{type: :float, description: "Position size in units"},
  returns_example: 0.1095
}
```

# `median_terminal_wealth_multiple`

```elixir
@spec median_terminal_wealth_multiple(
  fraction(),
  float(),
  fraction(),
  non_neg_integer()
) ::
  float() | {:error, :invalid_fraction | :invalid_bet_count}
```

Project the median terminal-wealth multiple after a number of bets.

## Parameters

  * `win_rate` - Probability of winning (0.0 to 1.0) (value)
  * `win_loss_ratio` - Average win divided by average loss (value)
  * `fraction` - Fraction of bankroll bet, greater than 0.0 and less than 1.0 (value)
  * `bet_count` - Non-negative number of bets (value)

## Returns

Median terminal bankroll divided by starting bankroll or an error tuple (`float`)

### Example

```elixir
1.7227
```

## Errors

  * `:invalid_fraction`
  * `:invalid_bet_count`

```elixir
# descripex:contract
%{
  params: %{
    fraction: %{
      description: "Fraction of bankroll bet, greater than 0.0 and less than 1.0",
      kind: :value
    },
    win_rate: %{
      description: "Probability of winning (0.0 to 1.0)",
      kind: :value
    },
    win_loss_ratio: %{
      description: "Average win divided by average loss",
      kind: :value
    },
    bet_count: %{description: "Non-negative number of bets", kind: :value}
  },
  errors: [:invalid_fraction, :invalid_bet_count],
  returns: %{
    type: :float,
    description: "Median terminal bankroll divided by starting bankroll or an error tuple"
  },
  returns_example: 1.7227
}
```

# `optimal_f`

```elixir
@spec optimal_f([number()]) :: fraction() | nil
```

Calculate Ralph Vince's optimal fixed fraction from trade history.

## Parameters

  * `trades` - List of trade results (positive for wins, negative for losses) (value)

## Returns

Optimal fraction of account to risk per trade, or nil if insufficient data (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    trades: %{
      description: "List of trade results (positive for wins, negative for losses)",
      kind: :value
    }
  },
  returns: %{
    type: :float,
    description: "Optimal fraction of account to risk per trade, or nil if insufficient data"
  },
  returns_example: 0.1095
}
```

# `simulate_kelly`

```elixir
@spec simulate_kelly(kelly_simulation_inputs()) ::
  {:ok,
   %{
     seed: integer(),
     rng_algorithm: :exsss,
     bet_count: pos_integer(),
     path_count: pos_integer(),
     paths: [kelly_path()],
     terminal_wealth: %{
       mean: float(),
       standard_error: float() | nil,
       quantiles: [%{quantile: number(), value: float()}]
     },
     log_growth_per_bet: %{mean: float(), standard_error: float() | nil},
     fraction_below_thresholds: [
       %{threshold: number(), fraction_below: float()}
     ]
   }}
  | {:error, kelly_simulation_error()}
```

Simulate seeded terminal wealth and drawdown for discrete Kelly bets.

## Parameters

  * `inputs` - Required map with :win_rate, :win_loss_ratio, :fraction, positive :bet_count/:path_count, integer :seed, :quantiles, and :thresholds (value)

## Returns

\{:ok, result\} with per-path outcomes, terminal-wealth quantiles/mean/standard error, log-growth uncertainty, and fractions below thresholds (`tuple`)

### Example

```elixir
{:ok,
 %{
   seed: 42,
   log_growth_per_bet: %{standard_error: 0.002, mean: 0.008},
   terminal_wealth: %{
     standard_error: 0.04,
     quantiles: [%{value: 1.09, quantile: 0.5}],
     mean: 1.18
   },
   fraction_below_thresholds: [%{threshold: 1.0, fraction_below: 0.31}]
 }}
```

## Errors

  * `:invalid_inputs`
  * `:invalid_win_rate`
  * `:invalid_win_loss_ratio`
  * `:invalid_fraction`
  * `:invalid_bet_count`
  * `:invalid_path_count`
  * `:invalid_seed`
  * `:invalid_quantiles`
  * `:invalid_thresholds`

```elixir
# descripex:contract
%{
  params: %{
    inputs: %{
      description: "Required map with :win_rate, :win_loss_ratio, :fraction, positive :bet_count/:path_count, integer :seed, :quantiles, and :thresholds",
      kind: :value
    }
  },
  errors: [:invalid_inputs, :invalid_win_rate, :invalid_win_loss_ratio,
   :invalid_fraction, :invalid_bet_count, :invalid_path_count, :invalid_seed,
   :invalid_quantiles, :invalid_thresholds],
  returns: %{
    type: :tuple,
    description: "{:ok, result} with per-path outcomes, terminal-wealth quantiles/mean/standard error, log-growth uncertainty, and fractions below thresholds"
  },
  returns_example: {:ok,
   %{
     seed: 42,
     log_growth_per_bet: %{standard_error: 0.002, mean: 0.008},
     terminal_wealth: %{
       standard_error: 0.04,
       quantiles: [%{value: 1.09, quantile: 0.5}],
       mean: 1.18
     },
     fraction_below_thresholds: [%{threshold: 1.0, fraction_below: 0.31}]
   }}
}
```

# `terminal_wealth_relative`

```elixir
@spec terminal_wealth_relative([number()], fraction()) ::
  float() | {:error, :invalid_fraction | :invalid_trades | :no_losing_trades}
```

Evaluate observed geometric-mean holding-period returns at a fraction.

## Parameters

  * `trades` - Observed numeric trade results with at least one loss (value)
  * `fraction` - Fraction of bankroll risked, greater than 0.0 and less than 1.0 (value)

## Returns

Geometric mean of normalized holding-period returns or an error tuple (`float`)

### Example

```elixir
1.0607
```

## Errors

  * `:invalid_fraction`
  * `:invalid_trades`
  * `:no_losing_trades`

```elixir
# descripex:contract
%{
  params: %{
    fraction: %{
      description: "Fraction of bankroll risked, greater than 0.0 and less than 1.0",
      kind: :value
    },
    trades: %{
      description: "Observed numeric trade results with at least one loss",
      kind: :value
    }
  },
  errors: [:invalid_fraction, :invalid_trades, :no_losing_trades],
  returns: %{
    type: :float,
    description: "Geometric mean of normalized holding-period returns or an error tuple"
  },
  returns_example: 1.0607
}
```

# `volatility_scaled`

```elixir
@spec volatility_scaled(number(), fraction(), number(), number()) :: position_size()
```

Calculate position size scaled by volatility.

## Parameters

  * `account_size` - Total account equity (value)
  * `risk_percent` - Base risk per trade as decimal (value)
  * `current_volatility` - Current volatility measure (e.g., ATR, std dev) (value)
  * `target_volatility` - Target/baseline volatility for normal sizing (value)

## Returns

Volatility-adjusted risk amount in account currency (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    account_size: %{description: "Total account equity", kind: :value},
    risk_percent: %{description: "Base risk per trade as decimal", kind: :value},
    current_volatility: %{
      description: "Current volatility measure (e.g., ATR, std dev)",
      kind: :value
    },
    target_volatility: %{
      description: "Target/baseline volatility for normal sizing",
      kind: :value
    }
  },
  returns: %{
    type: :float,
    description: "Volatility-adjusted risk amount in account currency"
  },
  returns_example: 0.1095
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
