DeltaCalc

Pure-Decimal calculation engine for leveraged crypto trading — position sizing, effective leverage, liquidation price, DCA ladders, safety scoring, spot hedging, funding-rate math, account metrics, margin-bridge financing, option-ladder strategies, position PnL, delta-neutral rebalancing, portfolio-margin netting, stress scenarios, fee math, spot/perp carry analysis, and cash-secured put collateral.

Salvaged from the retired TradingDashboard app so a rebuild does not reinvent the math. Every function is a pure value-in / value-out Decimal computation: no Ecto, no Phoenix, no I/O. Drop it into any Elixir project (LiveView, CLI, Nx pipeline, agent tool) and call it.

Installation

def deps do
[{:delta_calc, "~> 0.5.0"}]
end

Quick start

# In iex -S mix or any Elixir app with :delta_calc as a dependency
alias DeltaCalc.{
Calc,
Leverage,
Liquidation,
Allocation,
Safety,
Presets,
DCAPlanner,
Quantization,
PositionCalculator,
Hedging,
Funding,
AccountMetrics,
Concentration,
MarginBridge,
FundingProjection,
OptionLadder,
OptionsRisk,
Pnl,
DeltaNeutral,
CashSecuredPut,
PortfolioMargin,
StressScenario,
Fees,
Carry
}

DeltaCalc.Calc (compatibility façade)

Compatibility façade preserving the original entry points. The focused implementation modules are Leverage, Liquidation, Allocation, Safety, DCAPlanner, and Quantization.

# effective_leverage(notional, wallet_equity) -> Decimal
Calc.effective_leverage(Decimal.new(10_000), Decimal.new(5_000))
#=> #Decimal<2>
# leverage_to_aum(notional, total_aum) -> Decimal
Calc.leverage_to_aum(Decimal.new(10_000), Decimal.new(100_000))
#=> #Decimal<0.1>
# liquidation(entry, leff, mmr_total, side) -> Decimal
# Round at the caller's output boundary for display.
Calc.liquidation(Decimal.new(3000), Decimal.new(2), Decimal.new("0.005"), :long)
|> Decimal.round(2)
#=> #Decimal<1507.54>

DeltaCalc.Presets

Hardcoded risk modes, black-swan thresholds, and default DCA ladder steps.

# load_modes/0 -> %{conservative: %{pct:, cap:}, moderate: ..., aggressive: ...}
Presets.load_modes().conservative
#=> %{pct: #Decimal<0.01>, cap: #Decimal<0.01>}
# load_thresholds/0 -> %{"ETH" => %{long:, short:}, ...}
Presets.load_thresholds()["ETH"]
#=> %{long: 25, short: 25}
# load_dca_preset/0 -> [{price_pct, allocation_pct}, ...]
Presets.load_dca_preset()
#=> [
# {#Decimal<0.95>, #Decimal<0.30>},
# {#Decimal<0.90>, #Decimal<0.30>},
# {#Decimal<0.85>, #Decimal<0.30>}
# ]

DeltaCalc.DCAPlanner

Builds defensive/aggressive DCA presets and orchestrates full ladder calculations.

params = %{
defensive_prices: [Decimal.new("2850"), Decimal.new("2700")],
dca_allocations: [Decimal.new("30"), Decimal.new("30")]
}
# build_defensive_preset(params, entry_price, side) -> [{price_mult, alloc}, ...]
DCAPlanner.build_defensive_preset(params, Decimal.new("3000"), :long)
#=> [
# {#Decimal<0.95>, #Decimal<0.3>},
# {#Decimal<0.9>, #Decimal<0.3>}
# ]
dca_params = %{
params: Map.put(params, :dca_enabled, true),
position_with_tokens: %{
notional: Decimal.new("100"),
eff_lev: Decimal.new("2"),
tokens: Decimal.new("0.03333333")
},
dca_reserve: Decimal.new("50"),
entry_price: Decimal.new("3000"),
ui_leverage: Decimal.new("2"),
side: :long,
mmr_rate: Decimal.new("0.005"),
mark_buffer: Decimal.new("0.001"),
aum: Decimal.new("10000"),
black_swan_pct: Decimal.new("0.15")
}
# calculate_dca_ladder(dca_params) -> %{defensive: ..., aggressive: ...} | nil
DCAPlanner.calculate_dca_ladder(dca_params).defensive
#=> %{
# final_avg_entry: #Decimal<2910.638297872340425531914893617022>,
# final_eff_lev: #Decimal<3.2>,
# steps: [...],
# ...
# }

DeltaCalc.PositionCalculator

Full position-sizing pipeline from a plain params map.

params = %{
aum: Decimal.new("10000"),
side: :long,
entry_price: Decimal.new("3000"),
subaccount_allocation: Decimal.new("100"),
initial_position_pct: Decimal.new("0.5"),
black_swan_pct: Decimal.new("0.15"),
ui_leverage: Decimal.new("2"),
mmr_rate: Decimal.new("0.005"),
mark_buffer: Decimal.new("0.001")
}
result = PositionCalculator.calculate_position(params)
#=> %{
# effective_leverage: #Decimal<1.0>,
# leverage_to_aum: #Decimal<0.01>,
# allocation: %{sub_eq: ..., init_position: ..., reserve: ..., ...},
# position: %{notional: ..., eff_lev: ..., tokens: ...},
# safety: %{
# is_safe: true,
# liquidation_price: #Decimal<0E+3>,
# black_swan_price: #Decimal<2550.00>,
# ...
# },
# mmr_info: %{...}
# }

DeltaCalc.Hedging

Pure spot-hedging formulas: required CEX balance, coverage checks, and snapshot deltas.

# calculate_required_cex_balance(total_spot, hedge_percent) -> Decimal
Hedging.calculate_required_cex_balance(Decimal.new("100000"), Decimal.new("60"))
#=> #Decimal<60000.0>
# check_hedge_coverage(cex_value, total_spot, target_hedge_percent)
Hedging.check_hedge_coverage(Decimal.new("60000"), Decimal.new("100000"), Decimal.new("60"))
#=> {:ok, #Decimal<60.0>}
prior = %{
total_spot: Decimal.new("100000"),
cex_spot: Decimal.new("60000"),
cold_wallet: Decimal.new("40000"),
hedge_coverage_pct: Decimal.new("60"),
captured_at: ~U[2024-01-01 00:00:00Z]
}
current = %{
total_spot: Decimal.new("110000"),
cex_spot: Decimal.new("55000"),
cold_wallet: Decimal.new("55000"),
hedge_coverage_pct: Decimal.new("50"),
captured_at: ~U[2024-01-01 01:00:00Z]
}
# calculate_change(prior, current) -> %{total_change:, cex_change:, ...}
Hedging.calculate_change(prior, current)
#=> %{
# total_change: #Decimal<10000>,
# cex_change: #Decimal<-5000>,
# cold_change: #Decimal<15000>,
# hedge_change: #Decimal<-10>,
# duration_hours: 1.0
# }

DeltaCalc.Funding

Funding-rate APR annualisation, cross-venue comparison, arbitrage detection, and trend analysis.

# funding_apr(rate, period_hours) -> {:ok, %{hourly, daily, annual}} | {:error, :invalid_rate}
Funding.funding_apr(Decimal.new("0.0001"), 8)
#=> {:ok, %{hourly: #Decimal<0.0012500>, daily: #Decimal<0.0300>, annual: #Decimal<10.9500>}}
# compare_funding_rates(rates) -> comparison map per symbol
# `delta` is a raw per-period fraction (`delta_unit: :raw_per_period`), compared against a
# raw-per-period threshold derived from the caller's daily `:min_delta`.
Funding.compare_funding_rates(%{binance: Decimal.new("0.0001"), bybit: Decimal.new("0.00015")})
#=> %{delta: #Decimal<0.00005>, arbitrage_opportunity: false, delta_unit: :raw_per_period, ranked: [...], ...}
# funding_trend(series) -> {:ok, trend} | {:error, :insufficient_data}
Funding.funding_trend([Decimal.new("0.0001"), Decimal.new("0.00012"), Decimal.new("0.00015")])
#=> {:ok, %{trend: :increasing, slope: #Decimal<...>, volatility: #Decimal<...>, ...}}

DeltaCalc.AccountMetrics

Per-account liquidation, leverage, margin usage, and safety metrics.

account = %{
entry_price: Decimal.new("3000"),
notional: Decimal.new("10000"),
equity: Decimal.new("5000"),
margin_used: Decimal.new("1000"),
mmr_total: Decimal.new("0.005"),
side: :long,
swan_pct: Decimal.new("25")
}
# calculate(account, opts) -> %{effective_leverage, liquidation_price, ...}
AccountMetrics.calculate(account)
#=> %{
# effective_leverage: #Decimal<2>,
# liquidation_price: #Decimal<1507.537688442211055276381909547739>,
# liquidation_distance_pct: #Decimal<49.74874371859296482412060301507537>,
# margin_usage_pct: #Decimal<20.0>,
# safety: %{verdict: :tight, ...}
# }
# margin_usage_pct(margin_used, equity) -> Decimal
AccountMetrics.margin_usage_pct(Decimal.new("1000"), Decimal.new("5000"))
#=> #Decimal<20.0>

DeltaCalc.Concentration

Portfolio concentration risk via the Herfindahl-Hirschman Index (normalized 0–1 scale).

weights = %{
"BTC" => Decimal.new("0.45"),
"ETH" => Decimal.new("0.30"),
"SOL" => Decimal.new("0.15"),
"Others" => Decimal.new("0.10")
}
# hhi(weights) -> Decimal
Concentration.hhi(weights)
#=> #Decimal<0.3250>

DeltaCalc.MarginBridge

Perp-funded option financing: margin ratios, runway, payback timelines, and kill-switch checks. Single-scenario payback_timeline/3 differs from FundingProjection.project_payback_timeline/1 (volatility scenarios).

# margin_ratio(initial_margin, option_premium, capital) -> Decimal
MarginBridge.margin_ratio(Decimal.new("6000"), Decimal.new("2700"), Decimal.new("60000"))
#=> #Decimal<0.145>
# margin_runway_days(available_margin, daily_burn) -> Decimal | nil
MarginBridge.margin_runway_days(Decimal.new("2025"), Decimal.new("45"))
#=> #Decimal<45>
# payback_timeline(remaining_debt, daily_funding, opts) -> payback map
MarginBridge.payback_timeline(Decimal.new("2430"), Decimal.new("90"))
#=> %{remaining_debt: #Decimal<2430>, daily_funding: #Decimal<90>, days_to_payoff: #Decimal<27>, ...}
# stress_test_prolonged_negative(rate, position_size, days, opts) -> stress map
# the rate is a per-period fraction (-0.0001 = -0.01% per funding period), never a percent
MarginBridge.stress_test_prolonged_negative(
Decimal.new("-0.0001"),
Decimal.new("60000"),
90,
periods_per_day: 24
)
#=> %{daily_cost: #Decimal<144.0000>, total_cost: #Decimal<12960.0000>, kill_switch_day: nil, ...}
# check_kill_switch(per_period_funding_rate, margin_ratio, opts) -> kill-switch map
# daily_funding_rate = per_period_rate x :periods_per_day (default 3, overridable)
MarginBridge.check_kill_switch(Decimal.new("0.015"), Decimal.new("0.145"))
#=> %{kill_switch_triggered: false, per_period_funding_rate: #Decimal<0.015>,
# daily_funding_rate: #Decimal<0.045>, periods_per_day: #Decimal<3>, ...}

DeltaCalc.FundingProjection

Best-, expected-, and worst-case payback horizons from funding income and volatility. For a single-scenario timeline with optional payoff date, see DeltaCalc.MarginBridge.payback_timeline/3.

# project_payback_timeline(params) -> %{best_case, expected, worst_case}
# exact inputs only — floats are rejected by the `DeltaCalc.Decimal` boundary
FundingProjection.project_payback_timeline(%{
remaining_debt: 2700,
daily_funding: 90,
funding_volatility: "0.2"
})
#=> %{best_case: 25, expected: 30, worst_case: 38}

DeltaCalc.OptionLadder

Rolling option ladder math: expiry selection, roll decisions, strike ladders, and funding sync.

expiries = [
%{expiry: "2026-03-07", days_to_expiry: 7, liquidity: Decimal.new("1.5"), bid_ask_spread: Decimal.new("0.05")},
%{expiry: "2026-03-14", days_to_expiry: 14, liquidity: Decimal.new("1.2"), bid_ask_spread: Decimal.new("0.06")}
]
# optimal_expiries(expiries, opts) -> %{buckets, total_allocation}
OptionLadder.optimal_expiries(expiries)
#=> %{buckets: [%{bucket: :front, allocation: #Decimal<0.625>, ...}, ...], total_allocation: #Decimal<1.000>}
# check_roll_conditions(position, market) -> roll decision
position = %{days_to_expiry: 3, pnl_percent: "10", bid_ask_spread: "0.14"}
OptionLadder.check_roll_conditions(position, %{momentum: :flat})
#=> %{action: :roll, target: :next_weekly}
# iv_adjusted_size(base_size, opts) -> size adjustment map
OptionLadder.iv_adjusted_size(Decimal.new("100"), iv_percentile: 35)
#=> %{base_size: #Decimal<100>, adjusted_size: #Decimal<125.00>, action: :increase_size, ...}

DeltaCalc.OptionsRisk

Long-option risk framing, gross exposure, negative-funding stress, and margin-bridge health.

# max_loss(option_premiums) -> %{max_loss, risk_model, limited_downside}
OptionsRisk.max_loss(Decimal.new("2700"))
#=> %{max_loss: #Decimal<2700>, risk_model: :premium_only, limited_downside: true}
# calculate_total_exposure(legs) -> per-leg notionals + total_exposure
OptionsRisk.calculate_total_exposure(%{
spot_notional: Decimal.new("60000"),
perp_notional: Decimal.new("-60000"),
options_notional: Decimal.new("2700"),
margin_debt: Decimal.new("1800")
})
#=> %{spot_notional: #Decimal<60000>, total_exposure: #Decimal<124500>, ...}
# monitor_margin_bridge_health(params, opts) -> health map
OptionsRisk.monitor_margin_bridge_health(%{
initial_margin: Decimal.new("6000"),
option_premium: Decimal.new("2700"),
capital: Decimal.new("60000"),
available_margin: Decimal.new("2025"),
daily_burn: Decimal.new("45")
})
#=> %{margin_ratio: #Decimal<0.145>, runway_days: #Decimal<45>, health_status: :healthy}

DeltaCalc.Pnl

Position PnL, return-on-equity, and fee/funding-adjusted breakeven math.

# unrealized_pnl(params) -> Decimal
Pnl.unrealized_pnl(%{
entry_price: Decimal.new("50000"),
mark_price: Decimal.new("51000"),
size: Decimal.new("2"),
side: :long
})
#=> #Decimal<2000>
# realized_pnl(params) -> Decimal (fees + accrued funding netted)
Pnl.realized_pnl(%{
entry_price: Decimal.new("50000"),
exit_price: Decimal.new("52000"),
size: Decimal.new("2"),
side: :long,
open_fee_rate: Decimal.new("0.0004"),
close_fee_rate: Decimal.new("0.0002"),
accrued_funding: Decimal.new("15")
})
#=> #Decimal<3954.2000>
# roe(params) -> Decimal
Pnl.roe(%{pnl: Decimal.new("400"), margin: Decimal.new("1000")})
#=> #Decimal<40.0>
# breakeven(params) -> Decimal
# Round at the caller's output boundary for display.
Pnl.breakeven(%{
entry_price: Decimal.new("50000"),
size: Decimal.new("2"),
open_fee_rate: Decimal.new("0.0004"),
close_fee_rate: Decimal.new("0.0002"),
side: :long
})
|> Decimal.round(2)
#=> #Decimal<50030.01>

DeltaCalc.CashSecuredPut

cash_secured_put_coverage/1 calculates cash funding for a proposed put. Supply actual settled cash, a strike denominated in quote currency per base unit, a separate cash fee reserve, and disjoint existing cash commitments. All money currencies must match the strike quote currency; the quantity base currency must match the strike base currency. Currency identifiers are case-sensitive strings.

{:ok, funding} =
CashSecuredPut.cash_secured_put_coverage(%{
settled_cash: %{currency: "USD", value: "752.25"},
strike: %{base_currency: "ETH", quote_currency: "USD", value: "3000"},
quantity: %{
unit: :contracts,
base_currency: "ETH",
value: "2.5",
base_units_per_contract: "0.1"
},
fee_reserve: %{currency: "USD", value: "2.25"},
existing_commitments: []
})
funding
#=> %{
# base_amount: #Decimal<0.25>,
# base_currency: "ETH",
# cash_currency: "USD",
# existing_commitments: #Decimal<0>,
# fee_reserve: #Decimal<2.25>,
# fully_covered: true,
# remaining_capacity: #Decimal<0>,
# settled_cash: #Decimal<752.25>,
# strike_principal: #Decimal<750.00>,
# total_required: #Decimal<752.25>,
# uncovered_amount: #Decimal<0>
# }

This funds 0.25 ETH of strike principal (750 USD) and 2.25 USD of fees exactly. Results include Decimal strike_principal, fee_reserve, summed existing_commitments, total_required, remaining_capacity and uncovered_amount, plus fully_covered. Both capacity and shortfall are clamped at zero, after all obligations. Unreceived premium never reduces principal.

For an already-base amount, pass %{unit: :base_currency, base_currency: "ETH", value: "0.25"}; a multiplier on that shape is rejected. Contract quantities require a positive provider-supplied multiplier and may be fractional; the consumer owns provider eligibility rules. Exact inputs are Decimal, integer or decimal string, never floats. Nonfinite, negative, incompatible or ambiguous inputs return tagged errors. Zero amounts are valid. Calculations do not round to display precision or depend on the caller's Decimal precision. Coverage never grants provider margin acceptance, risk-target approval, or a reservation.

DeltaCalc.DeltaNeutral

Net delta aggregation and rebalance sizing from exchange-supplied position deltas.

positions = [
%{kind: :spot, size: Decimal.new("1.5"), side: :long},
%{kind: :perp, size: Decimal.new("1.0"), side: :short},
%{kind: :option, delta: Decimal.new("0.35")}
]
# net_delta(positions) -> Decimal
DeltaNeutral.net_delta(positions)
#=> #Decimal<0.85>
# rebalance_to_neutral(positions | params) -> rebalance map
DeltaNeutral.rebalance_to_neutral(positions)
#=> %{
# net_delta: #Decimal<0.85>,
# within_tolerance: false,
# side: :short,
# size: #Decimal<0.85>,
# instrument: :perp,
# signed_hedge: #Decimal<-0.85>
# }

Base-numeraire math for inverse perps, options, covered-call coverage, and risk targets:

# base_numeraire_exposure(params) -> {:ok, Decimal} | {:error, reason}
DeltaNeutral.base_numeraire_exposure(%{
kind: :inverse_perpetual,
quantity: %{unit: :usd_notional, value: Decimal.new("12000")},
mark: Decimal.new("3000")
})
#=> {:ok, #Decimal<4>}
DeltaNeutral.base_numeraire_exposure(%{
kind: :option,
quantity: %{unit: :base_currency, value: Decimal.new("1")},
delta: %{semantic: :black_scholes, value: Decimal.new("0.40")},
mark: %{unit: :quote_currency, value: Decimal.new("100"), spot_price: Decimal.new("2000")}
})
#=> {:ok, #Decimal<0.35>} # 1 x (0.40 - 100/2000); ambiguous shapes return named errors
# settlement_coverage(params) -> {:ok, coverage map} | {:error, reason}
DeltaNeutral.settlement_coverage(%{
eligible_base: Decimal.new("10"),
existing_short_call_obligations: Decimal.new("2"),
other_reservations: Decimal.new("0"),
pending_sell_reservations: Decimal.new("0"),
proposed_short_call_obligation: Decimal.new("5")
})
#=> {:ok, %{total_obligation: #Decimal<7>, remaining_capacity: #Decimal<3>,
# uncovered_amount: #Decimal<0>, fully_covered: true, ...}}
# risk_target(params) -> {:ok, target map} | {:error, reason}
# Coverage never implies neutrality: fully_covered does not mean within_target.
DeltaNeutral.risk_target(%{
base_numeraire_exposure: Decimal.new("0.65"),
target_exposure: Decimal.new("0"),
tolerance: Decimal.new("0.1")
})
#=> {:ok, %{residual_exposure: #Decimal<0.65>, within_target: false, ...}}

DeltaCalc.PortfolioMargin

Combined maintenance margin, netted liquidation price, and margin usage for a position book.

account = %{
equity: Decimal.new("1000"),
positions: [
%{side: :long, quantity: Decimal.new("3"), mark_price: Decimal.new("3000"), mmr: Decimal.new("0.005")},
%{side: :short, quantity: Decimal.new("1"), mark_price: Decimal.new("3000"), mmr: Decimal.new("0.005")}
]
}
# combined_maintenance_margin(account) -> Decimal
PortfolioMargin.combined_maintenance_margin(account)
#=> #Decimal<30.000>
# portfolio_liquidation_price(account) -> Decimal | nil
# Round at the caller's output boundary for display.
PortfolioMargin.portfolio_liquidation_price(account)
|> Decimal.round(2)
#=> #Decimal<2512.56>
# margin_usage(account) -> %{used, available, usage_pct}
PortfolioMargin.margin_usage(account)
#=> %{used: #Decimal<30.000>, available: #Decimal<970.000>, usage_pct: #Decimal<3.000>}

DeltaCalc.StressScenario

Price-shock scenarios and cascade liquidation simulation across a portfolio-margin book.

account = %{
equity: Decimal.new("1000"),
positions: [
%{id: :btc_long, side: :long, quantity: Decimal.new("3"), mark_price: Decimal.new("3000"), mmr: Decimal.new("0.005")},
%{id: :btc_short, side: :short, quantity: Decimal.new("1"), mark_price: Decimal.new("3000"), mmr: Decimal.new("0.005")}
]
}
# apply_shock(account, shock_pct) -> shock result map
StressScenario.apply_shock(account, Decimal.new("-10"))
#=> %{
# shock_pct: #Decimal<-10>,
# equity: #Decimal<400.0>,
# positions: [...],
# portfolio_margin: #Decimal<27.0000>,
# portfolio_liquidated?: false,
# liquidation_price: #Decimal<...>
# }
# cascade(account, shock_pct) -> cascade result map
StressScenario.cascade(account, Decimal.new("-20"))
#=> %{
# shock_pct: #Decimal<-20>,
# liquidated_positions: [:btc_long, :btc_short],
# margin_call: #Decimal<224.0000>,
# survives?: false
# }

DeltaCalc.Fees

Effective entry/exit prices, roundtrip cost, and funding-adjusted breakeven.

# effective_entry(fill_price, params) -> Decimal
Fees.effective_entry(Decimal.new("50000"), %{
fee_rate: Decimal.new("0.0004"),
slippage_bps: Decimal.new("10"),
side: :long
})
#=> #Decimal<50070.0000>
# effective_exit(fill_price, params) -> Decimal
Fees.effective_exit(Decimal.new("50000"), %{fee_rate: Decimal.new("0.0004"), side: :long})
#=> #Decimal<49980.0000>
# roundtrip_cost(params) -> Decimal
Fees.roundtrip_cost(%{
notional: Decimal.new("10000"),
open_fee_rate: Decimal.new("0.0004"),
close_fee_rate: Decimal.new("0.0002")
})
#=> #Decimal<6.0000>
# funding_adjusted_breakeven(entry_price, params, accrued_funding) -> Decimal
# Round at the caller's output boundary for display.
Fees.funding_adjusted_breakeven(
Decimal.new("50000"),
%{size: Decimal.new("2"), open_fee_rate: Decimal.new("0.0004"), close_fee_rate: Decimal.new("0.0002"), side: :long},
Decimal.new("0")
)
|> Decimal.round(2)
#=> #Decimal<50030.01>

DeltaCalc.Carry

Basis yield, break-even funding, and net carry for spot/perp hedge profitability.

# basis(spot_price, perp_price) -> Decimal (instantaneous premium/discount, not annualized)
Carry.basis(Decimal.new("60000"), Decimal.new("60600"))
#=> #Decimal<1.00>
# breakeven_funding(params) -> Decimal (per-period rate)
# Round at the caller's output boundary for display.
Carry.breakeven_funding(%{
spot_price: Decimal.new("60000"),
perp_price: Decimal.new("60600"),
holding_days: 30
})
|> Decimal.round(8)
#=> #Decimal<-0.00011111>
# net_carry(params) -> carry decision map
Carry.net_carry(%{
spot_price: Decimal.new("60000"),
perp_price: Decimal.new("60600"),
funding_rate: Decimal.new("0.0001"),
holding_days: 30
})
#=> %{
# basis: #Decimal<1.00>,
# basis_yield: #Decimal<1.00>,
# funding_yield: #Decimal<0.9000>,
# net_yield: #Decimal<1.9000>,
# breakeven_funding: #Decimal<-0.0001111111111111111111111111111111111>,
# profitable?: true
# }

Agent surface

Every public function carries an api/3 declaration (via Descripex) for agent discovery.

# JSON-serializable manifest of the full API
DeltaCalc.Manifest.build()
# MCP tool definitions for trading agents
DeltaCalc.Manifest.tools()
# Static export
mix descripex.manifest --pretty

Provenance

Extracted from TradingDashboard.Risk.* (the Calc/Presets/DCAPlanner/PositionCalculator modules) plus the pure hedging formulas from its Portfolio context. The original modules carry ~100 unit + StreamData property tests, ported alongside the code.

Development

mix deps.get
mix test # or: mix test.json
mix doctor # 100% @doc + @spec on public API
mix docs # ex_doc HTML output
mix descripex.manifest --pretty
mix precommit # format + credo + doctor + tests
mix precommit.full # + dialyzer