DeltaCalc.DCAPlanner (DeltaCalc v0.3.0)

Copy Markdown View Source

DCA ladder planning and strategy management.

Builds defensive and aggressive DCA presets, calculates reserve-funded ladder steps, and enhances those steps with portfolio metrics.

API Functions

FunctionArityDescriptionParam Kinds
enhance_dca_steps5Enhance DCA steps with leverage-to-AUM and black swan safety metrics.steps: value, aum: value, black_swan_pct: value, entry_price: value, side: value
build_aggressive_preset3Build aggressive DCA preset from user configuration or defaults.params: value, entry_price: value, side: value
build_defensive_preset3Build defensive DCA preset from user configuration or defaults.params: value, entry_price: value, side: value
calculate_dca_ladder1Calculate defensive and aggressive DCA ladder results when reserve is available.dca_params: value
convert_ladder_for_short1Convert a long DCA ladder preset to a short preset.long_preset: value
dca_ladder8Calculate DCA ladder steps using reserve allocation.position: value, reserve: value, entry_price: value, ui_lev: value, ladder_preset: value, side: value, mmr_rate: value, opts: value

Summary

Functions

Build aggressive DCA preset from user configuration or defaults.

Build defensive DCA preset from user configuration or defaults.

Calculates DCA ladder results if reserve is available and DCA is enabled.

Convert a long DCA ladder preset to a short preset.

Enhance DCA steps with additional risk and portfolio metrics.

Types

dca_params()

@type dca_params() :: %{
  params: map(),
  position_with_tokens: map(),
  dca_reserve: Decimal.t(),
  entry_price: Decimal.t(),
  ui_leverage: Decimal.t(),
  side: :long | :short,
  mmr_rate: Decimal.t(),
  mark_buffer: Decimal.t(),
  aum: Decimal.t(),
  black_swan_pct: Decimal.t()
}

dca_preset()

@type dca_preset() :: [{Decimal.t(), Decimal.t()}]

dca_result()

@type dca_result() :: %{optional(:defensive) => map(), optional(:aggressive) => map()}

dca_step()

@type dca_step() :: map()

mmr_schedule()

@type mmr_schedule() :: [{DeltaCalc.Decimal.input(), DeltaCalc.Decimal.input()}]

Functions

build_aggressive_preset(params, entry_price, side)

@spec build_aggressive_preset(map(), Decimal.t(), :long | :short) :: dca_preset()

Build aggressive DCA preset from user configuration or defaults.

Aggressive DCA goes with the current position direction:

  • For longs: buy at higher prices (momentum trading)
  • For shorts: buy at lower prices (momentum trading)

Parameters

  • params: Parameters map containing DCA price and allocation configuration
  • entry_price: Entry price for calculating price multipliers (Decimal)
  • side: Position side (:long or :short)

Returns

List of {price_multiplier, allocation_decimal} tuples.

Examples

params = %{
  aggressive_prices: [Decimal.new("3150"), Decimal.new("3300")],
  dca_allocations: [Decimal.new("40"), Decimal.new("30")]
}

build_aggressive_preset(params, Decimal.new("3000"), :long)
#=> [{Decimal.new("1.05"), Decimal.new("0.40")}, {Decimal.new("1.10"), Decimal.new("0.30")}]

build_defensive_preset(params, entry_price, side)

@spec build_defensive_preset(map(), Decimal.t(), :long | :short) :: dca_preset()

Build defensive DCA preset from user configuration or defaults.

Defensive DCA goes against the current position direction:

  • For longs: buy at lower prices (averaging down)
  • For shorts: buy at higher prices (averaging up)

Parameters

  • params: Parameters map containing DCA price and allocation configuration
  • entry_price: Entry price for calculating price multipliers (Decimal)
  • side: Position side (:long or :short)

Returns

List of {price_multiplier, allocation_decimal} tuples.

Examples

params = %{
  defensive_prices: [Decimal.new("2850"), Decimal.new("2700")],
  dca_allocations: [Decimal.new("40"), Decimal.new("30")]
}

build_defensive_preset(params, Decimal.new("3000"), :long)
#=> [{Decimal.new("0.95"), Decimal.new("0.40")}, {Decimal.new("0.90"), Decimal.new("0.30")}]

calculate_dca_ladder(dca_params)

@spec calculate_dca_ladder(dca_params()) :: dca_result() | nil

Calculates DCA ladder results if reserve is available and DCA is enabled.

Builds both defensive and aggressive DCA strategies and calculates complete ladder results with enhanced step information including safety metrics.

Parameters

  • dca_params: Map or struct containing all DCA parameters:
    • params: Validated parameters map containing DCA configuration
    • position_with_tokens: Position map with tokens calculation
    • dca_reserve: Available DCA reserve amount (Decimal)
    • entry_price: Entry price for the position (Decimal)
    • ui_leverage: UI leverage setting (Decimal)
    • side: Position side (:long or :short)
    • mmr_rate: Minimum margin requirement rate (Decimal)
    • mark_buffer: Buffer added to the MMR used for liquidation calculations (Decimal)
    • aum: Total Assets Under Management (Decimal)
    • black_swan_pct: Black swan threshold as decimal (0-1)

Returns

Map with DCA ladder results, or nil if no DCA available:

  • :defensive - Defensive DCA strategy results (if available)
  • :aggressive - Aggressive DCA strategy results (if available)

Each strategy contains:

  • :steps - List of enhanced DCA steps with safety metrics
  • Other fields from dca_ladder/8 result

Examples

dca_params = %{
  params: %{
    dca_enabled: true,
    defensive_prices: [Decimal.new("2850"), Decimal.new("2700")],
    dca_allocations: [Decimal.new("30"), Decimal.new("30")]
  },
  position_with_tokens: position,
  dca_reserve: reserve,
  entry_price: entry,
  ui_leverage: leverage,
  side: :long,
  mmr_rate: mmr,
  mark_buffer: buffer,
  aum: aum,
  black_swan_pct: swan_pct
}

calculate_dca_ladder(dca_params)
#=> %{
#     defensive: %{steps: [...], final_avg_entry: ...},
#     aggressive: %{steps: [...], final_avg_entry: ...}
#   }

convert_ladder_for_short(long_preset)

@spec convert_ladder_for_short(list()) :: list()

Convert a long DCA ladder preset to a short preset.

Parameters

  • long_preset - List of {price_mult, reserve_pct} pairs whose exact values use canonical decimal strings; native Elixir callers may also pass Decimal or integer. (value)

Returns

List of {price_mult, reserve_pct} tuples for shorts (list)

# descripex:contract
%{
  params: %{
    long_preset: %{
      description: "List of {price_mult, reserve_pct} pairs whose exact values use canonical decimal strings; native Elixir callers may also pass Decimal or integer.",
      kind: :value
    }
  },
  returns: %{
    type: :list,
    description: "List of {price_mult, reserve_pct} tuples for shorts"
  }
}

dca_ladder(position, reserve, entry_price, ui_lev, ladder_preset, side, mmr_rate, opts \\ [])

@spec dca_ladder(
  map(),
  Decimal.t(),
  Decimal.t(),
  Decimal.t(),
  list(),
  :long | :short,
  Decimal.t(),
  keyword() | Decimal.t()
) :: map()

Calculate DCA ladder steps using reserve allocation.

Parameters

  • position - Initial position with :notional and :eff_lev as canonical decimal strings; native Elixir callers may also pass Decimal or integer. (value)
  • reserve - Reserve available for DCA as a canonical decimal string; native Elixir callers may also pass Decimal or integer. (value)
  • entry_price - Initial entry price as a canonical decimal string; native Elixir callers may also pass Decimal or integer. (value)
  • ui_lev - UI leverage for new positions as a canonical decimal string; native Elixir callers may also pass Decimal or integer. (value)
  • ladder_preset - List of side-specific {price_mult, reserve_pct} pairs whose exact values use canonical decimal strings; native Elixir callers may also pass Decimal or integer; multipliers are used as supplied (value)
  • side - Position side (:long or :short) used for liquidation math (value)
  • mmr_rate - Minimum margin requirement rate as a canonical decimal string; native Elixir callers may also pass Decimal or integer. (value)
  • opts - Optional :mark_buffer added to the applicable MMR and :mmr_schedule list of {minimum_notional, mmr_rate} tiers; exact values use canonical decimal strings and native Elixir callers may also pass Decimal or integer; the highest applicable threshold wins (default: [], value)

Returns

Map with steps, final_avg_entry, final_notional, final_liq, final_eff_lev (map)

# descripex:contract
%{
  params: %{
    position: %{
      description: "Initial position with :notional and :eff_lev as canonical decimal strings; native Elixir callers may also pass Decimal or integer.",
      kind: :value,
      schema: %{
        "additionalProperties" => false,
        "properties" => %{
          "eff_lev" => %{"type" => "string"},
          "notional" => %{"type" => "string"}
        },
        "required" => ["notional", "eff_lev"],
        "type" => "object"
      }
    },
    opts: %{
      default: [],
      description: "Optional `:mark_buffer` added to the applicable MMR and `:mmr_schedule` list of {minimum_notional, mmr_rate} tiers; exact values use canonical decimal strings and native Elixir callers may also pass Decimal or integer; the highest applicable threshold wins",
      kind: :value
    },
    side: %{
      description: "Position side (:long or :short) used for liquidation math",
      kind: :value,
      schema: %{"enum" => ["long", "short"], "type" => "string"}
    },
    reserve: %{
      description: "Reserve available for DCA as a canonical decimal string; native Elixir callers may also pass Decimal or integer.",
      kind: :value,
      schema: %{"type" => "string"}
    },
    ui_lev: %{
      description: "UI leverage for new positions as a canonical decimal string; native Elixir callers may also pass Decimal or integer.",
      kind: :value,
      schema: %{"type" => "string"}
    },
    entry_price: %{
      description: "Initial entry price as a canonical decimal string; native Elixir callers may also pass Decimal or integer.",
      kind: :value,
      schema: %{"type" => "string"}
    },
    ladder_preset: %{
      description: "List of side-specific {price_mult, reserve_pct} pairs whose exact values use canonical decimal strings; native Elixir callers may also pass Decimal or integer; multipliers are used as supplied",
      kind: :value
    },
    mmr_rate: %{
      description: "Minimum margin requirement rate as a canonical decimal string; native Elixir callers may also pass Decimal or integer.",
      kind: :value,
      schema: %{"type" => "string"}
    }
  },
  returns: %{
    type: :map,
    description: "Map with steps, final_avg_entry, final_notional, final_liq, final_eff_lev"
  }
}

enhance_dca_steps(steps, aum, black_swan_pct, entry_price, side)

@spec enhance_dca_steps(
  [dca_step()],
  Decimal.t(),
  Decimal.t(),
  Decimal.t(),
  :long | :short
) ::
  [dca_step()] | {:error, atom()}

Enhance DCA steps with additional risk and portfolio metrics.

Adds leverage-to-AUM ratios and black swan safety checks to each DCA step for comprehensive risk assessment at each ladder level.

Parameters

  • steps: List of DCA steps from dca_ladder/8
  • aum: Total Assets Under Management (Decimal)
  • black_swan_pct: Black swan threshold as decimal (0-1)
  • entry_price: Entry price (Decimal)
  • side: Position side (:long or :short)

Returns

Enhanced list of DCA steps with additional fields:

  • :leverage_to_aum - Cumulative position size as percentage of total AUM
  • :passes_black_swan - Whether this step's liquidation passes black swan test
  • :black_swan_price - Black swan price level for reference

Examples

steps = [%{cumulative_notional: Decimal.new("1000"), new_liq: Decimal.new("2800"), ...}]

enhance_dca_steps(steps, Decimal.new("50000"), Decimal.new("0.15"), Decimal.new("3000"), :long)
#=> [%{..., leverage_to_aum: Decimal.new("0.02"), passes_black_swan: true, ...}]