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