Part: 4 of 18
About this series
This series documents the engineering evolution of a production-ready algorithmic trading platform in Python. It focuses on architecture, state management, execution, real-time data processing, persistence, and the engineering decisions that transformed a simple trading bot into a production-ready platform.
In Part 3: Building a Production-Ready Position Manager for Algorithmic Trading, I described the component responsible for maintaining persistent position state throughout the entire trade lifecycle.
Read Part 3 here:
https://dev.to/pydevtop/building-a-production-ready-position-manager-for-algorithmic-trading-55n4
The Position Manager could remember every open position.
The next challenge was deciding what should happen to those positions as market conditions continuously changed.
Project Website
This article is part of the engineering story behind the Bybit Signal Trading Platform.
If you'd like to learn more about the project, see additional screenshots, features and technical details, visit:
https://py-dev.top/application-software/bybit-signal-trading-bot
The Problem Was Never Stop Loss
If someone had asked me during the first weeks of development where the Stop Loss logic should live, I wouldn't have hesitated.
Inside the Trade Execution Engine.
Where else?
The engine already received TradingView webhooks.
It validated incoming requests.
It calculated Take Profit.
It opened positions.
Adding one more calculation felt completely natural.
The implementation looked something like this.
signal = receive_signal()
validate(signal)
entry = execute_order(signal)
stop_loss = calculate_stop_loss(entry)
take_profit = calculate_take_profit(entry)
Simple.
Readable.
Everything related to opening a trade existed in one place.
At that moment there was absolutely no reason to introduce another component.
There was only one trading pair.
Only one open position.
No persistence.
No restart recovery.
No Break Even.
No Trailing Stop.
No Spot/Futures differences.
No Reverse Signal.
No Telegram controls.
No analytics.
The architecture matched the complexity of the platform.
And that is exactly why it was dangerous.
The biggest architectural mistakes rarely look like mistakes when a project is small.
They usually look elegant.
Why Simple Systems Lie
One of the biggest traps in software engineering is believing that an architecture proven by a prototype is automatically suitable for production.
It isn't.
Prototype architectures optimize for speed.
Production architectures optimize for change.
Those are very different goals.
During the first prototype almost every decision looked correct.
Need another validation?
Add another if.
Need another parameter?
Pass another argument.
Need another calculation?
Place it below the previous one.
Nothing felt wrong because every new feature increased complexity by only a few lines of code.
Then months passed.
The project kept growing.
The first partial Take Profit appeared.
Soon after, Break Even was added.
Then Trailing Stop.
After that came Reverse Signals.
Dry Run mode.
Testnet.
Mainnet.
Spot trading.
Futures trading.
SQLite persistence.
Restart recovery.
Telegram control panel.
WebSocket market data.
Every feature looked completely independent.
Every pull request looked small.
But something interesting was happening.
The execution engine was slowly becoming responsible for things that had nothing to do with execution.
It started validating business rules.
Tracking position state.
Evaluating exposure.
Reacting to market events.
Updating persistent storage.
Managing lifecycle transitions.
Generating reports.
Nothing broke.
The architecture simply became harder to understand every month.
That kind of failure is much more dangerous than an exception.
Because nobody notices it immediately.
Execution Is Not Risk
The turning point happened when I stopped looking at the code and started looking at the questions the platform was trying to answer.
The trading strategy answered one question.
Should we open a position?
The execution engine answered another.
How do we open the position?
Then another question quietly appeared.
Are we still allowed to open this position?
At first glance those questions seem related.
Architecturally they are completely different.
A trading signal is an opinion.
Risk management is a policy.
One predicts the market.
The other protects the account.
Mixing those responsibilities eventually creates a component that is difficult to extend because every new feature touches existing logic.
Instead, the execution pipeline gradually evolved into something much cleaner.
TradingView Signal
│
▼
Trade Execution Engine
│
▼
Risk Evaluation
│
Approve / Reject
│
▼
Position Manager
│
▼
Exchange
Separating those responsibilities simplified almost every other component.
The Evolution of the Trade Execution Engine
The first version of the execution engine was surprisingly small.
Receive Signal
│
▼
Validate
│
▼
Create Order
│
▼
Exchange
That architecture survived exactly until the platform became stateful.
Once positions started living longer than a single order, everything changed.
Now the engine needed information that wasn't available anymore.
Questions appeared continuously.
Is TP1 already reached?
Is Break Even active?
Did Trailing Stop move?
How much quantity remains?
Is this Spot or Futures?
Has the application restarted?
Is another position already open?
None of those questions belonged to execution.
They belonged to state.
Or policy.
Or lifecycle.
Yet the execution engine knew about all of them.
Every new responsibility increased coupling between components.
Eventually changing one feature meant reading code completely unrelated to that feature.
That was a clear indication that the architecture had reached its limits.
When "One More if" Became an Architectural Problem
I don't remember the exact commit where the design became problematic.
There wasn't one.
Architectural debt rarely arrives in a single pull request.
It grows one harmless decision at a time.
It usually starts like this.
if tp1_hit:
move_stop_loss()
A week later.
if trailing_enabled:
update_trailing_stop()
Another week.
if reverse_signal:
close_position()
Then.
if dry_run:
simulate_order()
else:
execute_order()
None of those conditions are incorrect.
The problem is that every new condition teaches the component another responsibility.
After dozens of iterations the execution engine wasn't executing anymore.
It was becoming the platform itself.
That violated one of the oldest principles in software architecture.
A component should have a single reason to change.
The solution was not deleting those conditions.
The solution was asking a different question.
Which component should own this decision?
That question changed the architecture far more than any refactoring tool ever could.
Looking Beyond Stop Loss
By this point it became obvious that Stop Loss had never been the real problem.
Stop Loss was simply the first visible symptom.
The real issue was ownership.
Who owns trading state?
Who owns execution?
Who owns lifecycle?
Who owns policy?
Answering those questions eventually produced independent components that could evolve without constantly breaking each other.
The Position Manager became responsible for remembering.
The Trade Execution Engine returned to executing.
And risk slowly became something much larger than a single Stop Loss calculation.
In the next section we'll explore how position state fundamentally changed the entire risk model, and why introducing Break Even, Trailing Stop, and partial Take Profit forced the platform to think about risk as a continuously evolving process rather than a single calculation performed when a trade is opened.
flowchart LR
TV[TradingView Signal]
TE[Trade Execution Engine]
RM[Risk Evaluation]
PM[Position Manager]
EX[(Exchange)]
TV --> TE
TE --> RM
RM -->|Approved| PM
RM -->|Rejected| TE
PM --> EX
Position Manager and Risk
The previous chapter introduced the Position Manager as the component responsible for remembering the current state of every open position.
At first glance, that doesn't sound related to risk management.
It is.
In fact, the Position Manager became the foundation that made meaningful risk evaluation possible.
Without persistent position state, every incoming market update would have to be treated as if it were the first one.
That isn't how real trading platforms work.
Risk changes over time.
The Position Manager tells us where the position currently is.
The Risk layer decides what should happen next.
Those responsibilities complement each other.
They should never be merged.
Position State
│
▼
Position Manager
│
┌──────────────┴──────────────┐
▼ ▼
Trade Execution Engine Risk Evaluation
│ │
└──────────────┬──────────────┘
▼
Exchange
One component remembers.
The other evaluates.
That distinction dramatically reduced coupling throughout the platform.
Break Even Changed Everything
Initially Stop Loss was static.
The position opened.
The Stop Loss price was calculated.
Nothing changed afterwards.
That approach survived exactly until Break Even was implemented.
Once the first Take Profit had been reached, the original Stop Loss no longer represented the platform's understanding of acceptable risk.
The position had already realized profit.
The remaining quantity was smaller.
The maximum acceptable loss had changed.
The Risk layer no longer asked:
Where is the Stop Loss?
Instead it asked:
What is the correct risk profile now?
That is a much more interesting question.
Break Even was not simply another price calculation.
It represented a transition between two completely different lifecycle states.
Position Open
│
▼
Full Exposure
│
▼
TP1 Reached
│
▼
Break Even Activated
│
▼
Reduced Risk
The Position Manager stored the transition.
The Risk layer interpreted it.
The execution engine simply followed instructions.
Every component stayed focused on its own responsibility.
Trailing Stop Is Continuous Risk Evaluation
Trailing Stop introduced another architectural shift.
Unlike Break Even, which happens once, Trailing Stop continuously reacts to market movement.
Every price update potentially changes the exit conditions.
That meant risk was no longer evaluated only when a signal arrived.
It also had to be evaluated while the position was already open.
The platform gradually evolved from signal-driven processing into event-driven processing.
Every market update became another opportunity to reduce exposure.
The Position Manager remembered the highest price for long positions and the lowest price for short positions.
The Risk layer compared those values with the configured trailing percentage.
Only then could it determine whether the stop should move.
Notice what did not happen.
The Trade Execution Engine never needed to know how the trailing calculation worked.
It simply received a decision.
position = position_manager.get(symbol)
decision = risk_manager.evaluate(position, current_price)
if decision.action == "move_stop":
execution_engine.update_stop(decision.new_stop)
That tiny separation made future changes significantly easier.
When trailing logic changed, the execution engine remained untouched.
When order execution changed, trailing logic remained untouched.
The dependency became one-directional.
Instead of every component understanding every rule, each component understood only its own domain.
Reverse Signals Are Risk Decisions
Reverse Signals looked like an execution feature.
Receive an opposite signal.
Close the current position.
Open another one.
Simple.
Until existing positions entered the picture.
What happens if TP1 has already been reached?
What if Trailing Stop is active?
Should the platform immediately reverse?
Or should it close the remaining quantity first?
What if multiple markets behave differently?
Those questions are not execution questions.
They are risk questions.
The execution engine should never decide whether abandoning a profitable position is acceptable.
Its responsibility is simply to execute whatever decision has already been made.
That realization simplified the architecture again.
Instead of embedding more business rules inside order execution, Reverse Signal became another policy evaluated before execution.
Incoming Signal
│
▼
Current Position
│
▼
Risk Evaluation
│
Close?
Ignore?
Reverse?
│
▼
Execution Engine
As the platform matured, execution became progressively smaller.
Policy became progressively smarter.
That is exactly the direction healthy architectures tend to evolve.
Event-Driven Risk
One of the most significant architectural improvements happened almost accidentally.
Originally the platform reacted only to incoming TradingView webhooks.
Eventually it reacted to events.
That difference sounds subtle.
It wasn't.
Instead of repeatedly asking every component for information, the platform started broadcasting events.
Examples included:
- Position Opened
- Position Closed
- TP1 Reached
- Break Even Activated
- Trailing Stop Updated
- Stop Loss Triggered
- Reverse Signal Received
Every component reacted only to the events it actually understood.
flowchart LR
Signal --> Execution
Execution --> PositionOpened
PositionOpened --> PositionManager
PositionOpened --> Risk
MarketPrice --> Risk
Risk --> TrailingUpdated
TrailingUpdated --> Execution
Execution --> Exchange
The architecture became less dependent on direct function calls.
Instead, components communicated through meaningful events.
That reduced coupling and made future extensions considerably easier.
Adding another lifecycle event no longer required modifying every existing component.
Only interested components reacted.
Everyone else ignored it.
That is one of the strongest characteristics of production-ready event-driven systems.
Testing Strategy Changed Completely
One unexpected benefit of separating responsibilities was testing.
The earliest versions of the platform were difficult to test.
Every new feature eventually required exchange connectivity.
Eventually unit tests became integration tests.
That slowed development considerably.
After introducing dedicated responsibilities, testing became almost trivial.
The Position Manager could be tested independently.
The Risk layer could be tested independently.
The execution engine could be mocked.
Instead of building entire trading sessions, tests became deterministic.
position = Position(
entry_price=62000,
remaining_qty=0.5,
tp1_hit=True,
trailing_active=False
)
decision = risk_manager.evaluate(
position,
current_price=63250
)
assert decision.action == "move_stop"
Notice what is missing.
No exchange.
No API credentials.
No WebSocket connection.
No TradingView.
No market replay.
Only deterministic input.
Deterministic systems produce deterministic tests.
That dramatically reduced debugging time and increased confidence before every release.
Perhaps the biggest lesson wasn't about Stop Loss at all.
It was about ownership.
Once every component became responsible for only one problem, testing stopped being an obstacle and became another engineering tool.
Production Lessons
Looking back, one of the biggest surprises wasn't discovering a better algorithm.
It was discovering how little algorithms matter if the surrounding architecture cannot support them.
Early in the project I spent far more time thinking about indicators than software design.
EMA periods.
RSI thresholds.
MACD parameters.
Take Profit percentages.
Trailing distances.
Those decisions certainly influence trading performance.
But none of them determine whether the platform is maintainable six months later.
Architecture does.
The most valuable engineering lesson from this project wasn't learning how to calculate risk.
It was learning how to organize it.
Every production feature eventually forced another architectural decision.
Partial Take Profit required persistent state.
Break Even required lifecycle transitions.
Trailing Stop required continuous evaluation.
Reverse Signals required policy decisions.
Analytics required historical persistence.
Restart recovery required durable storage.
None of those features were difficult individually.
The challenge appeared when all of them needed to coexist without turning the codebase into a collection of special cases.
The solution was never adding more conditions.
The solution was reducing responsibilities.
Every component gradually became smaller.
The platform became larger.
Ironically, those two things happened at the same time.
Engineering Means Preparing for Unknown Features
One question appeared repeatedly during development.
"Where should this feature go?"
At first the answer was almost always the same.
Inside the Trade Execution Engine.
Later the answer changed completely.
Instead of asking where code should be added, the better question became:
Which component should own this responsibility?
That small change in mindset prevented many future architectural mistakes.
A Position Manager should not execute orders.
A Risk Engine should not send API requests.
An Exchange Client should not evaluate trading policy.
Analytics should never decide whether a position should close.
Every component should be replaceable without rewriting the entire platform.
That principle sounds obvious.
It rarely remains obvious after months of continuous feature development.
Good architecture isn't created once.
It is protected continuously.
Future Evolution
The current platform already supports production-oriented trading features.
TradingView Webhooks.
Spot and Futures execution.
Partial Take Profit.
Break Even.
Trailing Stop.
Reverse Signals.
Persistent position storage.
Analytics.
Restart recovery.
WebSocket market data.
Telegram management.
Yet the architecture intentionally leaves room for future growth.
One possible direction is portfolio-level risk.
Today every position is evaluated independently.
A future version may evaluate the entire account before approving new trades.
Current
BTCUSDT ─────┐
ETHUSDT ─────┤
SOLUSDT ─────┤
▼
Independent Risk
Future
BTCUSDT ─────┐
ETHUSDT ─────┤
SOLUSDT ─────┤
▼
Portfolio Risk Engine
│
Exposure Limits
Correlation Analysis
Daily Loss Limits
Sector Allocation
Another future improvement involves adaptive position sizing.
Today quantity is configured before trading begins.
A future Risk Engine could determine position size dynamically based on volatility, available capital or portfolio exposure.
Those improvements become possible because the architecture already separates execution from policy.
Replacing one Risk implementation should not require changing the execution pipeline.
That is exactly the flexibility modular systems are designed to provide.
Engineering Decisions That Changed the Platform
Several decisions fundamentally changed the direction of the project.
Not because they introduced new functionality.
Because they simplified everything that followed.
State became explicit.
Instead of reconstructing position state from exchange responses, the platform owns its own persistent state.
That made restart recovery and analytics straightforward.
Risk became continuous.
Instead of evaluating Stop Loss only when opening a trade, every market event became an opportunity to reassess exposure.
Risk transformed from a calculation into an ongoing process.
Components became independent.
Execution.
Risk.
Position Management.
Analytics.
Market Data.
Exchange Connectivity.
Each component now has a clearly defined responsibility.
That dramatically reduced coupling across the platform.
Events became the language of the platform.
Rather than allowing every module to call every other module directly, the system increasingly communicates through meaningful lifecycle events.
Position Opened.
TP1 Reached.
Break Even Activated.
Trailing Updated.
Position Closed.
Those events describe business activity instead of implementation details.
That distinction makes the architecture easier to evolve.
flowchart LR
Signal --> TradeEngine
TradeEngine --> Risk
Risk --> PositionManager
PositionManager --> Analytics
Risk --> Exchange
Exchange --> PositionManager
PositionManager --> Notifications
PositionManager --> Statistics
Analytics --> Reports
As the number of features increased, communication patterns actually became simpler.
That is usually a strong indication that an architecture is moving in the right direction.
Engineering Principle
One idea became increasingly clear throughout the development of this trading platform.
Risk management is not a function.
It is not a Stop Loss formula.
It is not a percentage.
It is not even a single component.
Risk management is a continuous conversation between market events, position state and business policy.
The execution engine should never decide whether a position is safe.
The Position Manager should never decide how an order is sent.
The Exchange Client should never decide when exposure should change.
Each component should answer one question exceptionally well.
Everything else belongs somewhere else.
That philosophy produced far cleaner code than any refactoring technique.
The best architecture wasn't created by adding abstractions.
It emerged by removing unnecessary responsibilities.
As the platform grew larger, individual components became easier to understand.
Paradoxically, that is often what healthy software evolution looks like.
What's Next
In the next chapter we'll move one layer higher.
So far we've focused on executing trades safely.
The next challenge is understanding performance over time.
We'll explore how the platform evolved from simple trade history into a production-ready analytics subsystem capable of measuring profitability, trading behavior, execution quality and long-term strategy performance.
Because opening profitable trades is only half the problem.
Understanding why they were profitable is what turns a trading bot into an engineering platform.
Learn More
This article covers only one part of the overall platform architecture.
If you'd like to explore the project, its features and follow future development, visit the project page:
Bybit Signal Trading Platform
https://py-dev.top/application-software/bybit-signal-trading-bot
There you'll find an overview of the platform, supported features and future updates.
SEO Keywords Covered
- algorithmic trading
- algorithmic trading platform
- Python trading bot
- Python trading architecture
- production-ready trading system
- production-ready risk management
- trading risk engine
- position manager
- trade execution engine
- TradingView webhook
- Bybit API
- WebSocket trading
- futures trading
- spot trading
- event-driven architecture
- software architecture
- trading platform architecture
- trading system design
- risk evaluation
- break even
- trailing stop
- reverse signal
- engineering blog
- backend engineering
- Python backend
- system design
Thank you for reading! If you enjoyed this engineering story, follow the series as we continue documenting the architectural evolution of a production-ready algorithmic trading platform built with Python.
Top comments (0)