AlgoDaily

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.

Modeling Starts Here

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:

  • Buy
  • Sell
  • Hold

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.

Feature Thinking

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:

  1. Compute the average close over a lookback window.
  2. Compare the latest close to that average.
  3. If price is far below the average, signal Buy.
  4. If price is far above the average, signal Sell.
  5. 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 Buy only 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:

  • oneMinuteBars
  • fifteenMinuteBars
  • optional feature snapshots

That keeps aggregation in the pipeline and decision logic in the model.

Multi-Timeframe Models

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?”

Ready For Testing

Open interactive view →