Part: 5 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 4: Building a Production-Ready Risk Management Engine for
Algorithmic Trading, I described how the platform evolved from simply
executing trades to continuously managing risk through Stop Loss, Break
Even, Trailing Stop, Reverse Signals, and Emergency Close.
Read Part 4 here:
The platform could now execute trades and protect capital.
The next question wasn't how to trade.
It was something much more uncomfortable.
Was the platform actually becoming better over time?
Project Website
This article is part of the engineering story behind the Bybit Signal Trading Platform.
The project itself continues to evolve beyond these articles.
You can learn more about the platform here:
https://py-dev.top/application-software/bybit-signal-trading-bot
Trading Without Analytics Is Guessing
A surprising number of algorithmic trading projects stop evolving the
moment they become capable of opening and closing positions
automatically.
From the outside, everything appears to work perfectly.
Signals arrive.
Orders are executed.
Risk management protects positions.
The bot continues trading day after day.
It feels like success.
Until someone asks a deceptively simple question:
How profitable is the strategy after fees, funding, and hundreds of
completed trades?
That question exposed a major weakness in my platform.
The trading engine knew how to execute trades.
The Position Manager knew how to track them.
The Risk Management Engine knew how to protect them.
But once a position was closed, the platform almost immediately forgot
everything that had happened.
The Problem With Looking Only at Profit
Early prototypes stored only the information required for execution.
Entry Price
Exit Price
Quantity
Profit
For a prototype this looked perfectly reasonable.
Unfortunately, profit is one of the least informative numbers you can
store.
Imagine two completed trades.
Trade A shows a profit of \$120.
Trade B shows \$105.
Most dashboards would immediately rank Trade A higher.
But after including trading fees and funding payments the picture
changes completely.
Trade A
Gross Profit: +120
Trading Fees: -42
Funding: -18
---------------------
Net Profit: +60
Trade B
Gross Profit: +105
Trading Fees: -6
Funding: 0
---------------------
Net Profit: +99
The trade that appeared worse actually generated substantially more
money.
Gross Profit was telling only half of the story.
That realization completely changed the architecture of the platform.
Every Closed Trade Became Historical Data
The first versions of the platform treated a completed position as the
end of the workflow.
Signal
│
▼
Trade Engine
│
▼
Exchange
│
▼
Position Closed
Finished.
Nothing remained except console logs.
A week later there was no reliable way to answer questions like:
- Which trading pair performed best?
- Which strategy produced the highest ROI?
- How much money was lost to fees?
- Which exit condition occurred most often?
- How long were winning positions typically held?
The information simply no longer existed.
Closing a trade ended both execution and knowledge.
That was a mistake.
A Closed Position Is Still Valuable
The turning point came from changing a single assumption.
A closed position should not disappear.
It should become historical data.
Every completed trade now generated a permanent analytical record.
Position Closed
│
▼
Financial Calculations
│
▼
Analytics Engine
│
▼
SQLite Database
│
▼
Reports
The Trade Engine no longer produced only execution.
It also produced knowledge.
This seemingly small architectural decision fundamentally changed how
the platform evolved.
Instead of relying on memory or log files, every engineering decision
could now be validated using historical data.
Execution Data Is Different From Analytics Data
The execution engine only needs enough information to manage a live
position.
The analytics engine asks completely different questions.
Execution asks:
- Can this order be executed?
- Should TP1 trigger?
- Should the Stop Loss move?
Analytics asks:
- Which strategies remain profitable after fees?
- Does leverage improve ROI?
- Which symbols generate the highest drawdown?
- Is the platform improving over time?
Those questions require data that the execution engine doesn't naturally
need.
That realization led to an important architectural principle.
Execution data and analytics data should evolve independently.
The analytics engine became its own subsystem instead of becoming a
collection of SQL queries scattered throughout the project.
The Trade Model Never Stopped Growing
One of the most interesting parts of the project wasn't the trading
engine.
It was the database.
Not because SQLite is particularly exciting, but because every new
column represented a question the platform previously couldn't answer.
The first schema was intentionally tiny.
symbol
entry_price
exit_price
qty
profit
At the time, that looked complete.
The platform could calculate whether a trade won or lost.
What else could possibly be needed?
The answer arrived after only a few days of testing.
Every New Question Added Another Field
The first missing value was trading fees.
Without them, profit was misleading.
gross_profit
trading_fee
Then funding appeared.
funding
Then leverage.
leverage
Then ROI.
roi
Then holding time.
holding_time
Then strategy.
strategy
Then signal source.
signal_source
Then close reason.
close_reason
The schema wasn't expanding because I wanted a bigger database.
It was expanding because the platform was becoming capable of asking
better questions.
Designing Analytics Instead of Logs
Console logs are useful while debugging.
They are almost useless for understanding months of trading.
A production platform needs structured information.
Instead of reading thousands of log lines, the platform stores
normalized trade records.
Trade Closed
│
▼
Trade Engine
│
▼
Financial Calculations
│
▼
Analytics Record
│
▼
SQLite
That record becomes immutable history.
Every report is generated from it.
Separating Responsibilities
One lesson learned during development was that analytics should never
leak into the Trade Engine.
The Trade Engine has one responsibility.
Receive Signal
│
▼
Manage Position
│
▼
Execute Orders
The Analytics Engine has another.
Read Closed Trade
│
▼
Calculate Metrics
│
▼
Persist History
│
▼
Generate Reports
Keeping these responsibilities independent dramatically simplified both
systems.
Adding a new report never required changing order execution logic.
Likewise, modifying trading behaviour never broke historical reports.
Analytics Service
Rather than scattering SQL queries throughout the project, all
analytical calculations were collected inside a dedicated service.
SQLite
│
▼
AnalyticsService
│
┌──┼───────────────┐
▼ ▼ ▼
ROI PnL Drawdown
│
▼
Formatter
│
▼
Telegram
The service became the single place responsible for transforming raw
trading history into meaningful information.
This separation also made unit testing significantly easier.
The trading engine could be tested independently from reporting.
The analytics engine could be tested using historical data without
connecting to an exchange.
Reports Became Features
Initially the platform produced only one report.
A completed trade.
Soon that wasn't enough.
The reporting layer gradually evolved.
Closed Trade
│
▼
Daily Report
│
▼
Monthly Statistics
│
▼
ROI
│
▼
Profit by Pair
│
▼
Best Trades
│
▼
Worst Trades
│
▼
Drawdown
Each report answered a different engineering question.
Instead of asking whether a single trade succeeded, the platform started
measuring whether the trading system itself was improving.
Measuring Improvement
Perhaps the biggest lesson was that analytics isn't about creating
attractive dashboards.
It is about reducing uncertainty.
Every metric exists to support a future engineering decision.
Without historical data, improvements become opinions.
With historical data, they become measurable experiments.
SQLite Was Enough
One architectural decision often surprises people.
The platform never needed a dedicated analytics database.
There was no Elasticsearch.
No ClickHouse.
No data warehouse.
SQLite was more than sufficient.
Why?
Because the analytics engine wasn't designed to process millions of
trades every second.
It was designed to answer engineering questions about a production
trading platform.
The amount of data remained relatively small while the value extracted
from each completed trade continued to grow.
Choosing SQLite kept deployment simple.
Every VPS already contained everything required to store, analyze and
report trading performance.
That simplicity became a feature rather than a limitation.
Analytics Drives Engineering Decisions
Perhaps the most important realization during development was that
analytics isn't built for traders.
It is built for engineers.
Every report eventually leads to another question.
Monthly Report
│
▼
Unexpected Losses
│
▼
Investigate Trades
│
▼
Improve Algorithm
│
▼
Deploy New Version
│
▼
Measure Again
Without analytics this loop is impossible.
Every improvement becomes intuition.
With analytics every improvement becomes measurable.
Analytics Completed the Feedback Loop
At this point the platform contained four major subsystems.
TradingView / Signal Engine
│
▼
Trade Execution Engine
│
▼
Position Manager
│
▼
Risk Management Engine
│
▼
Trading Analytics Engine
│
▼
Historical Knowledge
Execution generated positions.
The Position Manager maintained state.
The Risk Management Engine protected capital.
The Analytics Engine answered the only question that really mattered.
Was the platform becoming better after every release?
For the first time the answer could be supported by data instead of
opinion.
Engineering Lessons
Several lessons emerged while building the analytics subsystem.
Persist more data than you currently need.
Future reports almost always require information that seems unnecessary
today.
Separate execution from analytics.
Order execution should never depend on reporting logic.
Store raw financial values.
Gross profit, fees and funding should remain independent values instead
of being merged into a single number.
Reports are products.
A useful report is often more valuable than another trading indicator
because it helps improve the entire platform.
Engineering Principle
Trading engines execute decisions.
Analytics engines explain whether those decisions were correct.
Building an execution engine taught the platform how to trade.
Building an analytics engine taught it how to learn.
Those are very different capabilities.
The second one ultimately proved to be the more valuable.
What's Next
The Analytics Engine explains what happened.
The next challenge is understanding what is happening right now.
Every trading decision depends on receiving reliable market data with
minimal latency.
In the next chapter we'll explore how a production-ready Real-Time
Market Data Engine evolved from a simple WebSocket client into one of
the central components of the platform.
Next:
Designing a Real-Time Market Data Engine for Algorithmic Trading
The Analytics Engine Changed How the Platform Was Developed
Before the Analytics Engine existed, most changes were evaluated
informally.
A trade looked better.
A new risk rule appeared safer.
A modified configuration seemed more profitable.
But none of those conclusions were reliable.
Once historical data became available, development changed.
Every architectural improvement could be measured against the behaviour
of previous versions.
Change Trading Logic
│
▼
Deploy to Dry Run or Testnet
│
▼
Collect Completed Trades
│
▼
Generate Analytics
│
▼
Compare Results
│
▼
Keep, Adjust, or Revert
This feedback loop became one of the most important systems in the
entire platform.
The Analytics Engine was no longer just reporting what had happened.
It was influencing what would be built next.
The Difference Between Data and Knowledge
The database stored facts.
entry_price
exit_price
qty
fees
funding
net_profit
holding_time
close_reason
But facts alone are not useful.
The role of the Analytics Engine was to transform those facts into
engineering knowledge.
Raw Trade Records
│
▼
Aggregation
│
▼
Financial Metrics
│
▼
Performance Reports
│
▼
Engineering Decisions
For example, a list of losing trades is data.
Knowing that most losses came from one symbol, one strategy, or one exit
reason is knowledge.
That distinction shaped the entire reporting architecture.
Why the Formatter Became Its Own Component
At first, analytical calculations and Telegram formatting lived
together.
That worked while there was only one report.
Then reports multiplied.
Daily summaries needed one structure.
Trade history needed another.
Profit by pair required grouping.
Drawdown needed a different calculation and a different presentation.
The same data also needed to be reusable outside Telegram in the future.
That led to another separation.
AnalyticsService
│
▼
Structured Result
│
▼
ReportFormatter
│
▼
Telegram Message
The Analytics Service returns structured data.
The formatter decides how that data should be presented.
This kept calculations independent from the delivery channel.
A future web dashboard, REST API, or exported report could reuse the
same analytics without changing the calculation layer.
Telegram Became an Operational Interface
Telegram was originally used only for notifications.
A position opened.
A Take Profit triggered.
A Stop Loss closed a trade.
Later it became the main operational interface for analytics.
📊 Trading Analytics
├── Daily Report
├── Profit by Pair
├── Profit by Month
├── Fees
├── Funding
├── ROI
├── Win / Loss
├── Drawdown
├── Best Trades
├── Worst Trades
└── Trade History
This was a practical decision.
The platform already used Telegram for control and notifications.
Adding analytical reports there required no separate frontend, no
authentication system, and no additional deployment process.
The result was immediate operational visibility from any device.
Reports Had to Remain Honest
One of the easiest mistakes in trading analytics is to present numbers
that look precise but are based on incomplete data.
The platform therefore distinguishes between values obtained directly
from the exchange and values reconstructed locally.
For example, a completed futures trade may contain:
Financial Source: bybit_closed_pnl_aggregated
Or, when exchange history is unavailable:
Financial Source: local_futures_fallback
This field may look insignificant.
It is not.
It tells the operator how much confidence to place in the financial
result.
An analytics engine should never hide uncertainty behind a polished
report.
A Closed Trade Record
By the time the architecture matured, a completed trade contained enough
information to explain its entire lifecycle.
trade = {
"symbol": symbol,
"market": market,
"side": side,
"strategy": strategy,
"signal_source": signal_source,
"entry_price": entry_price,
"exit_price": exit_price,
"qty": qty,
"leverage": leverage,
"gross_profit": gross_profit,
"open_fee": open_fee,
"close_fee": close_fee,
"fees": total_fees,
"funding": funding,
"net_profit": net_profit,
"roi": roi,
"holding_time": holding_time,
"close_reason": close_reason,
"financial_source": financial_source,
"opened_at": opened_at,
"closed_at": closed_at,
}
This is not just a database row.
It is a complete explanation of what happened to the position.
The Full Analytics Architecture
flowchart TD
A[Trading Signal] --> B[Trade Execution Engine]
B --> C[Position Manager]
C --> D[Risk Management Engine]
D --> E[Position Closed]
E --> F[Financial Calculation]
F --> G[Trade Storage]
G --> H[(SQLite)]
H --> I[Analytics Service]
I --> J[Daily Report]
I --> K[Profit by Pair]
I --> L[ROI]
I --> M[Fees and Funding]
I --> N[Drawdown]
I --> O[Trade History]
J --> P[Report Formatter]
K --> P
L --> P
M --> P
N --> P
O --> P
P --> Q[Telegram Control Center]
The important detail is what does not happen.
The Trade Engine does not generate reports.
The Position Manager does not run SQL queries.
The Telegram bot does not calculate ROI.
Each component owns one responsibility.
That separation kept the system maintainable as the number of metrics
and reports continued to grow.
What I Would Do Differently
Looking back, analytics should have been designed earlier.
The first versions of the platform produced valuable execution data but
failed to preserve enough of it.
Several fields had to be added later.
Some old trades could never be fully reconstructed because the original
records did not contain fees, funding, signal source, or close reason.
That led to a simple rule for future systems:
Store the facts first. Decide how to report them later.
Reports change frequently.
Historical facts cannot be recreated after they are lost.
Analytics Must Be Independent From Execution
A reporting error should never block an order from being closed.
Execution remains the critical path.
Analytics happens after the trade lifecycle is complete.
Store Financial Components Separately
Never store only Net Profit.
Preserve Gross Profit, fees, funding, and other financial components
independently.
Keep the Source of Financial Data
Knowing whether a result came from exchange history or a local
calculation is essential for debugging and trust.
Build Reports Around Decisions
A metric is useful only when it can influence a future engineering or
trading decision.
Simplicity Is Often Enough
SQLite and Telegram were sufficient because they solved the actual
operational problem without creating unnecessary infrastructure.
Engineering Principle
A trading platform without analytics can execute decisions.
It cannot prove whether those decisions were good.
The Trade Execution Engine taught the platform how to act.
The Position Manager taught it how to remember.
The Risk Management Engine taught it how to protect capital.
The Trading Analytics Engine taught it how to learn.
That completed the first major feedback loop in the architecture.
Learn More
This article is part of the engineering story behind the Bybit Signal
Trading Platform.
The series focuses on the architectural decisions, failures, and
production lessons that shaped the system rather than publishing the
complete source code.
You can explore the platform, its architecture, supported features, and
current development status here:
https://py-dev.top/application-software/bybit-signal-trading-bot
What's Next
The Analytics Engine explains what already happened.
The next challenge is understanding what is happening in the market
right now.
Every signal, position update, Stop Loss, Trailing Stop, and analytical
result ultimately depends on one foundation:
Reliable real-time market data.
In the next article, we'll explore how a simple WebSocket connection
evolved into a dedicated market data subsystem responsible for
subscriptions, reconnections, dynamic trading pairs, event routing, and
continuous price delivery across the platform.
Next:
Designing a Real-Time Market Data Engine for Algorithmic Trading
SEO Keywords
Python trading analytics engine
algorithmic trading analytics
trading platform architecture
SQLite trading database
trading performance metrics
ROI and drawdown calculation
crypto trading analytics
Bybit trading platform
Telegram trading reports
production-ready trading system
Top comments (0)