# Calculator Business Logic → PHP Backend Migration — Assessment & Recommended Approach

| | |
|---|---|
| **Status** | Proposed — not started. No code has been moved, extracted, or restructured. This document is a scoping/risk assessment, not a finished design — the exact endpoint shape, function signatures, and migration steps still need to be worked out in a dedicated planning session when this work actually begins. |
| **Document type** | Architecture assessment / future work reference |
| **Scope** | Moving all business logic (lot-sizing, risk/margin math, warnings/insights generation, TP-ladder handling, tick-snapping, live-rate resolution) out of `calculator-source.html`'s client-side JavaScript and into a canonical PHP engine, leaving only presentation (HTML/CSS/UI-interaction JS) on the frontend. |
| **Created** | 2026-08-07 |
| **Origin** | Kevin's explicit request, immediately after a session that fixed a real production bug in `AnalyticsEngine::getChallengeMetrics()` and documented the platform-wide "single source of truth" architectural lesson (see CLAUDE.md's "Single Source of Truth Rule (Core Account Metrics)" and the matching dated entry at the top of "Currently Being Worked On") — the calculator migration is a related but separate concern: not correctness-duplication risk, but **exposure risk**. Once TraderAlign is publicly hosted, the calculator's client-side JS is fully readable by anyone, exposing a year of iteration spent tuning its output to match real MT5 execution behavior. |

> **Read this document before starting any implementation work on the calculator migration.** It captures the concrete evidence behind the risk assessment (not just an opinion), the specific things that make this harder than a typical "move logic to backend" job, and a recommended approach — including a verification strategy that's uniquely available in this codebase and should not be skipped.

---

## Why this matters (the business reason, not just the technical one)

The calculator (`calculator-source.html`) is TraderAlign's most heavily-iterated single piece of logic — per Kevin, roughly a year of refinement to make its lot-sizing, margin, and risk math track real MT5 broker behavior as closely as possible. Once hosted publicly, any client-side JavaScript is fully visible to anyone who opens dev tools — there is no way to keep frontend JS confidential. That's fine for presentation code, but not for logic Kevin considers a competitive/proprietary asset. The fix is architectural, not obfuscation: the frontend should only ever collect inputs and render outputs; every actual computation should happen server-side in PHP, the same way every other core computation in this codebase already does (`AnalyticsEngine`, `JourneySegmentation`, `EdgeHealthEngine` — see CLAUDE.md's Architecture section for the established pattern this migration should follow).

## Current state — concrete facts, not estimates (checked directly against the live file, 2026-08-07)

- `calculator-source.html` is **6,448 lines**, with **~98 uniquely-named functions**, served as a single static HTML file via `readfile()` (confirmed in CLAUDE.md's Drafts section — no PHP processing happens on this file at all today).
- The entire calculation flow — both risk modes, warnings/insights generation, margin math, tick-snapping, multi-TP handling, and DOM output writing — lives inside **one 1,047-line `calculateBtn` click-event handler** (lines ~3625–4672), not a set of small pure functions. Business logic and DOM access are directly interleaved: `document.getElementById(...)` calls happen **inside** the calculation, not before it (243 `getElementById` calls across the file total).
- There are **two parallel calculation paths in the same handler** — "RR-Based Mode" and the primary auto/fixed/manual mode — which have already partially drifted from each other. Confirmed evidence: two near-duplicate pip-value functions (`pipValueAtPrice2` and `pipValueAtPrice`), and CLAUDE.md's own fix history for the RR-notation wording bug had to touch "7 separate `.toFixed(2) + ':1'` string-concatenation sites across both calculation modes" because they'd never shared one formatter.
- A **56-instrument specification table** (`var INSTRUMENTS = {...}`, pip multiplier/pip value/rate type/tick value/min lot/min account per instrument) plus a separate `MARGIN_PER_LOT` table drive the math. `AnalyticsEngine::INSTRUMENT_SPECS` (`api/analytics/engine.php`) is **already a partial, manually-maintained mirror** of part of this table — CLAUDE.md already flags this as a real, currently-live sync-drift risk ("a direct, field-by-field transcription... must be kept in sync if the calculator's own table changes").
- **Live cross-currency rate resolution happens client-side**, from the browser, hitting four different fallback third-party APIs directly (`freeforexapi.com`, `frankfurter.app`, `exchangerate-api.com`, and a jsDelivr-hosted currency-rates JSON) — this is not proprietary logic, but it is a real dependency this migration has to account for, since PHP has no equivalent fetch/cache/fallback layer for this today.
- The calculator already makes several real backend calls (`/api/account/state`, `/api/trades/validate`, `/api/trades/draft`) — so a "frontend calls PHP, PHP computes, frontend renders" round trip is not a new pattern for this file, just a new *scope* of what gets sent server-side.

## Why this is a real refactor, not a quick move

1. **Business logic and DOM access aren't currently separated, even internally.** Before any of this can move to PHP, it effectively has to be un-interleaved from the DOM first — that's real work independent of the language it ends up in.
2. **Two calculation paths, already drifted.** A faithful migration has to carry both modes over as-written, preserving whatever inconsistencies already exist between them (documented above) — reconciling them into one implementation is real, valuable, but *separate* follow-up work. Mixing "port to PHP" with "also clean up the duplication" in one pass is the most likely way subtle MT5-matching precision gets silently disturbed.
3. **Live-rate fetching needs a real decision, not just a code move.** Either (a) PHP grows its own fetch+fallback+cache layer for the same four (or fewer) providers, or (b) the frontend keeps fetching a rate and passes it into the PHP calculation as an input (defensible — a live FX rate isn't the proprietary part). This needs to be decided deliberately, not discovered mid-port.
4. **Precision risk is the real stake, not a hypothetical.** A year of tuning against MT5 means the danger isn't "does it still run," it's "does it silently produce a different number after refactor" — floating-point rounding order, tick-snap sequencing, and intermediate truncation points all matter here in ways that are easy to disturb even with good intentions and passing manual spot-checks.

## Why this is tractable, not a rewrite-from-scratch risk

1. Once separated from the DOM, the core is fundamentally a **pure function**: `(account balance, entry/SL/TP prices, instrument, risk mode, risk%, [live rate if needed]) → (lot size, risk $, margin, warnings[])`. Nothing in it needs a browser-only API except the live-rate fetch.
2. This codebase already has the exact right architectural pattern for "one canonical PHP engine, many consumers" — `AnalyticsEngine`, `JourneySegmentation`, `EdgeHealthEngine` are all proven examples of this shape already working in production. This is not a new kind of thing to build here.
3. **A uniquely strong verification path already exists and should be the backbone of the whole migration: golden-value regression against real, already-saved production data.** Every real trade in the `trades` table already stores both its calculator *inputs* (`entry_price`, `sl_price`, `tp_price`/`tp2_price`...`tp10_price`, `risk_pct`, `lots_per_position`, `instrument`, `risk_mode`, `balance_at_entry`, etc.) and the calculator's real *output* (`max_potential_profit`, `max_potential_loss`, `risk_reward_tp1`, `warnings_fired`, etc.) from when it was originally calculated. Before the new PHP engine is ever trusted, it should be run against every real historical row and checked for exact agreement with what's already stored — a genuine regression net built from real production history, not synthetic test cases someone has to invent.
4. **The UX cost is smaller than it sounds.** This is already not a live-as-you-type calculator — it recomputes on a `calculateBtn` click. A server round-trip on that same click is not a real regression in feel; the current UX pattern already accommodates a request/response cycle.
5. **This migration is also the natural moment to finally resolve the `AnalyticsEngine::INSTRUMENT_SPECS` vs. calculator `INSTRUMENTS` sync-drift risk** that CLAUDE.md already documents as live today — once the instrument table has one real home (PHP), `engine.php` can consume it directly instead of maintaining its own manual mirror.

## Recommended approach, when this work is actually planned

- Extract to a single canonical class (e.g. `src/CalculatorEngine.php`, matching this codebase's established naming/placement convention for shared engines) — pure functions, no DOM/session/HTTP dependencies baked in, so it's independently testable.
- Port the `INSTRUMENTS`/`MARGIN_PER_LOT` tables verbatim as PHP arrays first, reconciling with `AnalyticsEngine::INSTRUMENT_SPECS` as part of the same pass (closing that documented gap, not leaving a third copy).
- Port both calculation paths (RR-mode and auto/fixed/manual) faithfully, as-is, including their current inconsistencies — do **not** attempt to unify or "clean up" the two paths in the same pass; treat de-duplication as an explicit, separate, later task once the port itself is verified safe.
- Decide the live-rate strategy explicitly (server-side fetch+cache vs. client-fetches-and-passes-in) before writing the port, not as an afterthought.
- New endpoint (e.g. `POST /api/calculator/compute`) that the frontend calls on the same `calculateBtn` click trigger it already uses today — the frontend becomes: collect form values → POST → render the returned numbers/warnings. No change to the user-facing interaction model.
- **Before cutover: run the new PHP engine against every real historical trade's saved inputs and confirm exact agreement with the real, already-saved outputs.** Treat any disagreement as a blocking bug to resolve, not an acceptable rounding difference, given how much intentional tuning produced the current numbers.
- Only after that verification passes should the new engine become authoritative and the old inline JS logic be removed from `calculator-source.html`.

## Open questions to resolve when planning begins (not answered here)

- Exact live cross-currency rate strategy (see above) — this has real implications for latency, third-party dependency exposure, and caching.
- Whether to reconcile the RR-mode vs. auto/fixed/manual-mode duplication as an immediate follow-up or a deferred cleanup — recommend deferred, per the precision-risk reasoning above, but this is Kevin's call to make explicitly.
- Whether any part of the warnings/insights generation (currently tightly coupled to intermediate calculation variables inside the same handler) should be restructured into its own named step, or ported as-is alongside the rest of the calculation logic.
- How much of the current instant, no-round-trip UI feedback (price-mismatch checks, tick-snap preview, margin bar) needs to stay fully client-side for responsiveness vs. how much can move to the same server round-trip as the main calculation.
