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 |
Summary
Types
Fraction of bankroll (0.0 to 1.0)
One simulated path's terminal wealth and maximum peak-to-trough drawdown
Named input error returned by simulate_kelly/1
Caller-supplied inputs for a seeded discrete-Kelly simulation
Position size in units or currency
Functions
Calculate anti-martingale position adjustment.
Calculate position size using fixed fractional method.
Sample expected log-growth over a caller-supplied fraction range.
Calculate optimal position size using Kelly criterion.
Calculate expected logarithmic bankroll growth per bet.
Calculate position size based on maximum loss amount.
Project the median terminal-wealth multiple after a number of bets.
Calculate Ralph Vince's optimal fixed fraction from trade history.
Simulate seeded terminal wealth and drawdown for discrete Kelly bets.
Evaluate observed geometric-mean holding-period returns at a fraction.
Calculate position size scaled by volatility.
Types
@type fraction() :: float()
Fraction of bankroll (0.0 to 1.0)
@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
@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
@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
@type position_size() :: float()
Position size in units or currency
Functions
@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
0.1095# 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
}
@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
0.1095# 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
}
@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
[{0.1, 0.015}, {0.2, 0.0201}]Errors
:invalid_fraction:invalid_fraction_range:invalid_step
# 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}]
}
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
0.1095# 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
}
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
0.0201Errors
:invalid_fraction
# 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
}
@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
0.1095# 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
}
@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
1.7227Errors
:invalid_fraction:invalid_bet_count
# 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
}
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
0.1095# 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
}
@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
{: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
# 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}]
}}
}
@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
1.0607Errors
:invalid_fraction:invalid_trades:no_losing_trades
# 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
}
@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
0.1095# 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
}