Packages

Pure-function trading analytics for Elixir — Black-Scholes pricing and IV, options chain analytics (max pain, GEX, skew, surfaces), funding rates, basis, volatility estimators, risk metrics, position sizing, orderflow, and portfolio analytics. Self-describing for AI agents.

Current section

Files

Jump to
zen_quant mix.exs
Raw

mix.exs

defmodule ZenQuant.MixProject do
  use Mix.Project

  @version "0.11.2"
  @source_url "https://github.com/ZenHive/zen_quant"

  def project do
    [
      app: :zen_quant,
      version: @version,
      elixir: "~> 1.18",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      aliases: aliases(),
      docs: docs(),
      package: package(),
      source_url: @source_url,
      dialyzer: dialyzer()
    ]
  end

  def cli do
    [preferred_envs: ["test.json": :test, "dialyzer.json": :dev]]
  end

  # Pure-function library: no supervision tree, no started processes.
  def application do
    [extra_applications: [:logger]]
  end

  defp deps do
    [
      # Dev/test tooling
      {:ex_unit_json, "~> 0.6", only: [:dev, :test], runtime: false},
      {:dialyzer_json, "~> 0.2", only: [:dev, :test], runtime: false},
      {:styler, "~> 1.4", only: [:dev, :test], runtime: false},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:ex_doc, "~> 0.40", only: :dev, runtime: false},
      {:doctor, "~> 0.23", only: [:dev, :test], runtime: false},
      {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
      {:ex_dna, "~> 1.5", only: [:dev, :test], runtime: false},
      # Overridden past reach's `~> 0.12.0` pin: dev/test-only tooling, and
      # `mix reach.check` is exercised by `mix ci` — a real break shows up there.
      {:ex_ast, "~> 0.13.0", only: [:dev, :test], runtime: false, override: true},

      # VibeKit baseline (credo/dialyxir/ex_dna above are shared with it)
      {:ex_slop, "~> 0.4", only: [:dev, :test], runtime: false},
      {:reach, "~> 2.6", only: [:dev, :test], runtime: false},
      {:vibe_kit, "~> 0.1", only: [:dev, :test], runtime: false},

      # Tidewave for Claude Code MCP integration (non-Phoenix needs bandit)
      {:tidewave, "~> 0.5", only: :dev},
      {:bandit, "~> 1.10", only: :dev},

      # Self-describing API declarations.
      # `~> 1.0`, not a single-minor pin: descripex 1.0.0 is 0.13.0 with no
      # behavioural change, adopting semantic versioning over a named stable
      # surface. The tight pin existed because a minor had broken a public
      # return type once (0.11 -> 0.12 turned `short_name` from an atom into a
      # string); under the 1.x promise a minor can no longer do that, so that
      # reason is retired.
      {:descripex, "~> 1.0"},

      # Integration tests + Tidewave exploration with real exchange data.
      {:bourse, "~> 0.9.0", only: [:dev, :test]}
    ]
  end

  defp aliases do
    [
      tidewave: [
        "run --no-halt -e 'Agent.start(fn -> Bandit.start_link(plug: Tidewave, port: 4011) end)'"
      ],
      "check.fast": [
        "format --check-formatted",
        "compile --warnings-as-errors",
        "credo --strict --ignore TagTODO,TagFIXME"
      ],
      precommit: [
        "format --check-formatted",
        "compile --warnings-as-errors",
        "credo --strict --ignore TagTODO,TagFIXME",
        "doctor --raise",
        # preferred_envs (cli/0) is ignored for alias steps — set MIX_ENV
        # explicitly. `mix cmd` runs System.cmd directly (no shell) on Elixir
        # 1.20+, so wrap in `sh -c` for the env-var prefix to take effect.
        "cmd sh -c 'MIX_ENV=test mix test.json --quiet --cover --cover-threshold 85 --summary-only --exclude integration'",
        "sobelow --skip"
      ],
      "precommit.full": ["precommit", "dialyzer.json --quiet"],
      # Harness baseline; reviewers select focused behavior and risk checks.
      "check.dispatch": ["format --check-formatted", "compile --warnings-as-errors"],
      ci: [
        "precommit.full",
        "ex_dna --max-clones 0",
        "reach.check --arch --dead-code --smells --strict",
        # Fail loudly if AGENTS.md drifted from CLAUDE.md (+ transitive
        # @-imports), so cross-family reviewers never gate against stale rules.
        # The sync script lives in the operator's claude-marketplace checkout;
        # on machines without it (CI runners, other clones) skip with a notice
        # instead of failing the gate on an unrelated missing file.
        ~s(cmd sh -c 'SYNC=$HOME/_DATA/code/claude-marketplace/scripts/sync-agents-md.sh; if [ -x "$SYNC" ]; then "$SYNC" --check; else echo "AGENTS.md freshness check SKIPPED: sync-agents-md.sh not found"; fi')
      ]
    ]
  end

  defp dialyzer do
    [
      # OOM mitigation: skip transitive deps (default is :app_tree).
      # Tidewave/bandit's HTTP stack (plug, finch, mint, gun, cowlib, etc.) is
      # not in lib/ call graph and bloats the PLT to ~800 modules.
      plt_add_deps: :apps_direct,
      plt_add_apps: [:mix],
      # PLTs in priv/ survive `_build` wipes (avoids 5–10min rebuilds).
      plt_local_path: "priv/plts",
      plt_core_path: "priv/plts",
      ignore_warnings: ".dialyzer_ignore.exs"
    ]
  end

  defp package do
    [
      description:
        "Pure-function trading analytics for Elixir — Black-Scholes pricing and IV, options chain " <>
          "analytics (max pain, GEX, skew, surfaces), funding rates, basis, volatility estimators, " <>
          "risk metrics, position sizing, orderflow, and portfolio analytics. Self-describing for AI agents.",
      licenses: ["MIT"],
      links: %{
        "GitHub" => @source_url,
        "Changelog" => "https://hexdocs.pm/zen_quant/changelog.html"
      },
      maintainers: ["ZenHive"],
      files: ~w(lib .formatter.exs mix.exs README.md CHANGELOG.md LICENSE SKILLS.md)
    ]
  end

  defp docs do
    [
      main: "readme",
      extras: ["README.md", "SKILLS.md", "CHANGELOG.md"],
      source_url: @source_url,
      source_ref: "v#{@version}",
      groups_for_extras: [Guides: ["SKILLS.md"]],
      groups_for_modules: [
        "Options — pricing & greeks": [
          ZenQuant.Options.Pricing,
          ZenQuant.Options.Pricing.FiniteDifference,
          ZenQuant.Options.ExpiryGreeks,
          ZenQuant.Options.ModelDeviation,
          ZenQuant.Greeks,
          ZenQuant.Options.Probability
        ],
        "Options — chain analytics": [
          ZenQuant.Options,
          ZenQuant.Options.GammaWalls,
          ZenQuant.Options.Skew,
          ZenQuant.Options.Surface,
          ZenQuant.Options.ZeroDTE,
          ZenQuant.Options.BlockTrades,
          ZenQuant.Options.Chain,
          ZenQuant.Options.Snapshot,
          ZenQuant.Options.Deribit
        ],
        "Rates & carry": [
          ZenQuant.Funding,
          ZenQuant.Basis
        ],
        "Volatility & statistics": [
          ZenQuant.Volatility,
          ZenQuant.MeanReversion,
          ZenQuant.PowerLaw
        ],
        "Trend & momentum": [
          ZenQuant.Trend,
          ZenQuant.Fisher
        ],
        "Risk & sizing": [
          ZenQuant.Risk,
          ZenQuant.Sizing,
          ZenQuant.Portfolio
        ],
        "Market microstructure": [
          ZenQuant.Orderflow,
          ZenQuant.OrderBook,
          ZenQuant.MM,
          ZenQuant.Execution,
          ZenQuant.OrderState,
          ZenQuant.WS
        ],
        "Recording & backtesting": [
          ZenQuant.Recorder.JSONL,
          ZenQuant.Recorder.Replay,
          ZenQuant.Backtest
        ],
        "Display helpers": [
          ZenQuant.Helpers.Funding,
          ZenQuant.Helpers.Greeks,
          ZenQuant.Helpers.Risk
        ],
        "API discovery": [
          ZenQuant.Manifest,
          Mix.Tasks.ZenQuant.Manifest
        ]
      ]
    ]
  end
end