Technical Journal — Egis Trading System

What I have done

Today’s work focused on correctness, data integrity, and long-term reliability of Egis, especially around position lifecycle, signal execution, and repairability.

  1. Fixed the TP/SL sync design for closed positions
    • Clarified that when Binance closes a position via TP or SL, simply marking positions.status = closed is not sufficient.
    • Defined the correct sync flow:
      • Pull actual closing order (TP or SL) from Binance.
      • Persist that order into Egis as an immutable fact.
      • Fetch related trades and compute realised PnL and fees.
      • Backfill missing close metadata into internal positions.
    • Reinforced the principle that:Internal positions are the source of truth, but their correctness depends on derived exchange facts being persisted.
  2. Designed and corrected a repair command for wrong closed positions
    • A one-off Artisan command (egis:sync-wrong-closed-positions) was built to repair historical inconsistencies.
    • Discovered a critical flaw:
      • Positions were already marked closed, so the command incorrectly skipped all of them.
    • Updated the repair logic to:
      • Ignore “already closed” as a skip condition.
      • Actively scan TP and SL order IDs on Binance.
      • Pull and store both orders into Egis.
      • Determine the real closing order based on FILLED status.
      • Recalculate realised PnL and fees from trade data.
      • Backfill missing close fields (close_exchange_order_idclose_priceclosed_atrealised_pnlfee).
    • Ensured the command is:
      • Idempotent
      • Dry-run by default
      • Safe to run repeatedly
      • Verbose enough for evidence-based debugging
  3. Hardened signal execution to prevent duplicate positions
    • Added a strict rule:
      • If a bot already has an open internal position for a symbol, signal execution must not place a new order.
    • Implemented a backend guard based on:
      • positions table only (single source of truth).
    • Ensured skipped executions are explicitly logged with reason existing_open_position.
    • This prevents:
      • Double exposure
      • Budget corruption
      • Race-condition driven duplicates
  4. Prevented duplicate signal_key usage at bot creation
    • On User Dashboard:
      • Users are no longer allowed to create a Signal Hook bot with a duplicated signal_key.
    • Validation is backend-enforced:
      • Applies only when strategy = Signal Hook.
      • Allows editing an existing bot without changing its own key.
    • This avoids:
      • Ambiguous signal routing
      • Hard-to-debug multi-bot executions on the same signal
  5. Fixed a PHP 8.2 strict typing bug in ClosePositionService
    • Encountered runtime error:abs(): Argument #1 must be int|float, string given
    • Root cause:
      • Numeric values from DB or Binance came in as strings.
      • PHP 8.2 no longer allows numeric strings in abs().
    • Fixed by:
      • Explicit casting to (float) before math operations.
    • This reinforced the need for strict numeric normalization in financial code.
  6. Enabled soft delete for bots in User Dashboard
    • Implemented soft deletes for bots:
      • Added deleted_at.
      • Used SoftDeletes in model.
    • Added user-facing delete action with confirmation.
    • Enforced safety rules:
      • Cannot delete a bot with open positions.
      • Deleted bots are excluded from:
        • User listings
        • Signal execution selection
    • History (orders, positions) remains intact and auditable.

What I have learned

  1. “Closed” is not a state — it’s a conclusion
    • A position being closed without:
      • a closing order,
      • realised PnL,
      • fees,
      • timestamps,
        is effectively corrupted data.
    • Closure must always be backed by immutable exchange facts.
  2. Repairability must be first-class, not an afterthought
    • Systems interacting with exchanges will desync.
    • Designing a repair command early forces clarity about:
      • What is source of truth
      • What can be recomputed
      • What must be persisted forever
  3. Skipping logic is as important as execution logic
    • Most bugs today came from “skip too early” decisions:
      • “Position already closed → skip”
      • “Bot already exists → allow duplicate”
    • Correct systems require:
      • Explicit reasons for skipping
      • Evidence-based skipping, not state-based assumptions
  4. Derived data must be persisted before it’s trusted
    • You cannot calculate realised PnL unless:
      • Orders are stored
      • Trades are fetched
    • “Derivable later” is not enough — persistence enables:
      • Audits
      • Repairs
      • Deterministic UI
  5. PHP 8.2 enforces financial discipline
    • Silent numeric coercion is gone.
    • This is good.
    • Financial systems should always:
      • Normalize
      • Cast
      • Be explicit
    • Bugs now surface earlier, closer to the source.
  6. User constraints simplify backend complexity
    • Preventing duplicate signal_key usage:
      • Reduced ambiguity across the whole signal pipeline.
    • Soft-deleting bots instead of hard deletes:
      • Preserved history
      • Reduced edge cases
      • Simplified reasoning about past executions
  7. Internal positions truly are the backbone
    • Every decision today reinforced the original design:UI, budget, risk, and behavior must read from internal positions only.
    • Exchange data exists to correct and enrich, never to replace.