What I have done
1. Rebuilt the Available Budget model (again, but correctly this time)
I redefined available budget as a first-class accounting concept, not a derived guess from balances.
The final authoritative formula is now:
available_budget_usd =
initial_budget_usd
- transaction_fee_total
- funding_fee_total
- sum(open_positions.risk_amount_usd)
+ realised_pnl_total
+ unrealised_pnl_total
Key changes:
- Risk is reserved, not implied.
Each open position now explicitly reserves itsrisk_amount_usd, which is subtracted from available budget. - Fees and PnL are internal-only.
No Binance calls during UI render or budget calculation. - Budget logic lives in a single service, not scattered across controllers or UI.
This made the budget explainable, auditable, and predictable.
2. Persisted risk_amount_usd at position open time
Previously, risk was implicit and recomputed dynamically.
Now:
risk_amount_usdis stored on the position when it opens- It is immutable for that position’s lifetime
- Available budget becomes a simple sum instead of a fragile recomputation
This change turned “risk” from a calculation artifact into accounting data.
3. Cleaned up risk-based sizing (removed hidden safety logic)
I simplified risk-based sizing to do one thing only: calculate size.
Final sizing logic:
loss_percent = |entry - stop| / entry
computed_size_usd = risk_amount_usd / loss_percent
final_size_usd = computed_size_usd
Important removals:
- ❌ No capping
final_size_usdto available budget - ❌ No hidden clamps or “safety” adjustments
Why this works:
- Eligibility is checked elsewhere (budget checks, symbol locks)
- Sizing remains mathematically pure
- No duplicated or contradictory safeguards
This separation removed ambiguity and made failures easier to reason about.
4. Added explicit order blocking rules (instead of silent adjustment)
Instead of silently modifying sizes, the system now blocks orders explicitly when rules are violated.
New blocking rules:
- If
risk_amount_usdviolates available budget rules → order blocked - If another bot in the same account already has an OPEN position for the same symbol → order blocked
Blocked means:
- No order record created
- No Binance call
- Clear, logged reason
This keeps orders immutable and decisions visible.
5. Added observability where it actually matters
To validate risk sizing in real conditions:
- Created a dedicated
risk-sizing.log - Logged only when
signal_key = 'TEST' - Logged real runtime values (entry, SL, budget, risk, size)
This avoided log noise and gave me high-signal evidence.
After reviewing the report, I realized:
- The logic was correct
- The data wasn’t representative enough
- The solution was not more math, but better sampling
So I tightened logging instead of changing logic.
6. Made the budget explainable in the UI
On the User Dashboard → Bot Card, I added a verbose breakdown explaining:
- Initial budget
- Fees
- Reserved risk (open positions)
- Realised PnL
- Unrealised PnL
- Final available budget
The UI remains calm by default, with details tucked behind expand/collapse.
This turned “Why is my budget like this?” into a self-answering question.
7. Stabilized the system by running and fixing all tests
After multiple deep changes:
- I ran the full test suite
- Updated factories for new fields
- Fixed assertions to reflect the new budget and sizing rules
- Ensured:
- SQLite only
- No Binance calls
- No silent behavior changes
Green tests became a confirmation, not a guess.
What I have learned
1. Risk must be stored, not inferred
If risk is recomputed every time:
- Budget becomes unstable
- Decisions depend on “current state”
- Audits become impossible
Persisting risk_amount_usd turned risk into ledger data, not a formula.
2. Sizing and eligibility must never be mixed
Capping size based on budget felt “safe” but was actually dangerous:
- It hid decision logic
- It duplicated rules
- It created silent behavior changes
Correct architecture:
- Sizing → pure math
- Eligibility → explicit checks
- Blocking → visible, logged decisions
3. If a rule matters, block — don’t adjust
Silent adjustment creates:
- Confusing outcomes
- Impossible debugging
- False confidence
Explicit blocking:
- Preserves immutability
- Preserves intent
- Preserves trust in the system
4. Observability should be surgical, not noisy
Logging everything teaches nothing.
Logging:
- real data
- in real conditions
- behind a strict filter (
signal_key = 'TEST')
gave me clarity without chaos.
5. “Calm UX” is an architectural outcome
The dashboard feels calm now because:
- Numbers are stable
- Decisions are deterministic
- Explanations exist
Calm UI wasn’t designed — it emerged from correct accounting and clear rules.
6. When Cursor struggles, the problem is usually ambiguity
Most friction wasn’t technical — it was semantic:
- “budget” meant different things
- “risk” was both a percent and a value
- “safety” existed in multiple layers
Once rules were written authoritatively and minimally, Cursor followed.
Closing thought
Egis is now closer to what it wants to be:
A system where every number can be explained,
every decision can be audited,
and nothing happens silently.
That’s not faster trading.
That’s trustworthy trading.
