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.
- Fixed the TP/SL sync design for closed positions
- Clarified that when Binance closes a position via TP or SL, simply marking
positions.status = closedis 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.
- Clarified that when Binance closes a position via TP or SL, simply marking
- 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.
- Positions were already marked
- 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
FILLEDstatus. - Recalculate realised PnL and fees from trade data.
- Backfill missing close fields (
close_exchange_order_id,close_price,closed_at,realised_pnl,fee).
- Ensured the command is:
- Idempotent
- Dry-run by default
- Safe to run repeatedly
- Verbose enough for evidence-based debugging
- A one-off Artisan command (
- 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:
positionstable 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
- Added a strict rule:
- 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.
- Users are no longer allowed to create a Signal Hook bot with a duplicated
- 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
- On User Dashboard:
- 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.
- Explicit casting to
- This reinforced the need for strict numeric normalization in financial code.
- Encountered runtime error:
- Enabled soft delete for bots in User Dashboard
- Implemented soft deletes for bots:
- Added
deleted_at. - Used
SoftDeletesin model.
- Added
- 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.
- Implemented soft deletes for bots:
What I have learned
- “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.
- A position being closed without:
- 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
- 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
- Most bugs today came from “skip too early” decisions:
- 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
- You cannot calculate realised PnL unless:
- 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.
- User constraints simplify backend complexity
- Preventing duplicate
signal_keyusage:- Reduced ambiguity across the whole signal pipeline.
- Soft-deleting bots instead of hard deletes:
- Preserved history
- Reduced edge cases
- Simplified reasoning about past executions
- Preventing duplicate
- 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.
