Building Trading Models
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Modeling Starts Here
From clean data to decisions
In Introduction to Algorithmic Trading, you framed a trading system as a lifecycle: idea → data → model → execution controls.
In Data Retrieval and Preprocessing, you built the handoff: parsed candles, aligned time, aggregated larger bars, and normalized inputs.
Now we are ready for the modeling layer: the part that turns prepared time series into a trading intention.
A good model should answer:
- What market condition do I think exists?
- What signal does that imply?
- How confident or constrained is the decision?
- What state must be remembered between bars?
Today we will keep the scope tight: building model logic in C++. We will not judge profitability yet — that belongs to Backtesting and Performance Evaluation.

Model Boundary
Keep the model honest
Your model should not know how to call an exchange, open a socket, or download CSV files. Those responsibilities belong elsewhere.
A clean model boundary looks like this:
Prepared bars/features ──> Model ──> Signal
The model consumes already-aligned inputs and returns an intention such as:
BuySellHold
This separation helps you test ideas quickly. You can swap a momentum model for a parabolic curvature model without rewriting your data pipeline.
```cpp
#include <iostream>
#include <vector>
#include <string>
struct Candle {
long long timestamp;
double open, high, low, close;
};
enum class Signal { Buy, Sell, Hold };
std::string toString(Signal s) {
switch (s) {
case Signal::Buy: return "Buy";
case Signal::Sell: return "Sell";
default: return "Hold";
}
}
class MomentumModel {
public:
Signal predict(const std::vector<Candle>& bars) const {
if (bars.size() < 2) return Signal::Hold;
const Candle& prev = bars[bars.size() - 2];
const Candle& last = bars[bars.size() - 1];
double change = (last.close - prev.close) / prev.close;
if (change > 0.01) return Signal::Buy;
if (change < -0.01) return Signal::Sell;
return Signal::Hold;
}
};
int main() {
std::vector<Candle> bars = {
{1, 100, 102, 99, 100},
{2, 100, 104, 99, 102},
{3, 102, 103, 98, 99}
};
MomentumModel model;
std::cout << "Latest signal: " << toString(model.predict(bars)) << "\n";
return 0;
}
```
Feature Thinking
A model sees features, not chaos
Raw candles contain useful information, but models usually work better when you express that information as features.
Examples:
- Recent return:
close[t] / close[t-1] - 1 - Range size:
(high - low) / close - Distance from average:
close - movingAverage - Curvature: whether recent price movement is accelerating upward or downward
- Multi-timeframe agreement: 1-minute trend agrees with 15-minute trend
Think of a feature as a compressed question about the market:
“Is price moving, stretching, compressing, reverting, or accelerating?”
For innovative alternative algorithms, this mindset is powerful: you are not limited to common indicators. You can encode shape, rhythm, volatility clustering, or parabolic acceleration as model inputs.

Model Responsibility Check
Which responsibility belongs most directly inside the trading model layer?
Rule Model
A first practical model: mean distance
Let’s build a simple rule model:
- Compute the average close over a lookback window.
- Compare the latest close to that average.
- If price is far below the average, signal
Buy. - If price is far above the average, signal
Sell. - Otherwise,
Hold.
This is not a profitability claim. It is a clean example of model construction: state-free input, deterministic feature calculation, and explicit signal output.
```cpp
#include <iostream>
#include <vector>
#include <numeric>
#include <string>
#include <iomanip>
struct Candle {
long long timestamp;
double open, high, low, close;
};
enum class Signal { Buy, Sell, Hold };
std::string toString(Signal s) {
if (s == Signal::Buy) return "Buy";
if (s == Signal::Sell) return "Sell";
return "Hold";
}
class MeanDistanceModel {
int lookback;
double threshold;
public:
MeanDistanceModel(int lookbackBars, double thresholdPct)
: lookback(lookbackBars), threshold(thresholdPct) {}
Signal predict(const std::vector<Candle>& bars) const {
if (static_cast<int>(bars.size()) < lookback) return Signal::Hold;
double sum = 0.0;
for (int i = bars.size() - lookback; i < static_cast<int>(bars.size()); ++i) {
sum += bars[i].close;
}
double avg = sum / lookback;
double latest = bars.back().close;
double distance = (latest - avg) / avg;
std::cout << std::fixed << std::setprecision(4);
std::cout << "Average: " << avg << ", latest: " << latest
<< ", distance: " << distance << "\n";
if (distance < -threshold) return Signal::Buy;
if (distance > threshold) return Signal::Sell;
return Signal::Hold;
}
};
int main() {
std::vector<Candle> bars = {
{1, 100, 101, 99, 100},
{2, 100, 102, 99, 101},
{3, 101, 103, 100, 102},
{4, 102, 104, 101, 103},
{5, 103, 104, 94, 95}
};
MeanDistanceModel model(5, 0.03);
Signal signal = model.predict(bars);
std::cout << "Signal: " << toString(signal) << "\n";
return 0;
}
```
Mutation Trap
True or false: A model’s predict function should freely mutate raw historical candles so later predictions can reuse adjusted values.
Parabolic Curvature
Modeling acceleration, not just direction
Momentum asks: “Did price move up or down?”
A parabolic feature asks a different question:
“Is the movement accelerating upward or downward?”
Using three equally spaced closes, we can approximate curvature:
curvature = y[t] - 2*y[t-1] + y[t-2]
Interpretation:
- Positive curvature: price path bends upward
- Negative curvature: price path bends downward
- Near zero: movement is closer to linear
This is a compact example of the course’s parabolic math theme. It is also a good alternative-model ingredient because it captures shape, not just level.
```cpp
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <iomanip>
struct Candle {
long long timestamp;
double open, high, low, close;
};
enum class Signal { Buy, Sell, Hold };
std::string toString(Signal s) {
if (s == Signal::Buy) return "Buy";
if (s == Signal::Sell) return "Sell";
return "Hold";
}
class CurvatureModel {
double minCurvature;
public:
explicit CurvatureModel(double threshold) : minCurvature(threshold) {}
Signal predict(const std::vector<Candle>& bars) const {
if (bars.size() < 3) return Signal::Hold;
double y0 = bars[bars.size() - 3].close;
double y1 = bars[bars.size() - 2].close;
double y2 = bars[bars.size() - 1].close;
double curvature = y2 - 2.0 * y1 + y0;
std::cout << std::fixed << std::setprecision(4);
std::cout << "Closes: " << y0 << ", " << y1 << ", " << y2 << "\n";
std::cout << "Curvature: " << curvature << "\n";
if (curvature > minCurvature) return Signal::Buy;
if (curvature < -minCurvature) return Signal::Sell;
return Signal::Hold;
}
};
int main() {
std::vector<Candle> upwardBend = {
{1, 100, 101, 99, 100},
{2, 101, 103, 100, 102},
{3, 103, 108, 102, 107}
};
CurvatureModel model(1.0);
std::cout << "Signal: " << toString(model.predict(upwardBend)) << "\n";
return 0;
}
```
Signal Vocabulary
Complete the sentence:
A trading model should output a signal, not execute an ______.
Multi-Timeframe Models
Use higher timeframes as context
Because you already built larger bars in Data Retrieval and Preprocessing, your model can use multiple timeframes without mixing responsibilities.
One useful pattern is context + trigger:
Higher timeframe: market context
Lower timeframe: entry trigger
Example:
- 15-minute bars say the broader trend is upward
- 1-minute bars show a short-term pullback
- Model outputs
Buyonly when both conditions agree
This does not require the model to build the bars itself. It only consumes already-prepared series.
A clean model signature might receive:
oneMinuteBarsfifteenMinuteBars- optional feature snapshots
That keeps aggregation in the pipeline and decision logic in the model.

Ready For Testing
What you built conceptually
You now have the core modeling mindset:
- Models consume prepared time series
- Features express market questions
- Signals represent intention, not execution
- C++ classes help isolate model behavior
- Alternative ideas can be encoded as shape-based features, including parabolic curvature
- Multi-timeframe models should use prebuilt aligned bars rather than rebuilding data internally
A strong model is not necessarily complex. It is clear, testable, and explicit about what information it uses.
Next, we will take these signals into Backtesting and Performance Evaluation, where the question changes from “Can the model produce decisions?” to “How did those decisions behave over historical data?”
