AlgoDaily

Developing Trading Algorithms

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

From Data To Decisions

Why this lesson matters

Now that we've covered Market Data Acquisition, you can parse incoming trades or book updates and maintain usable market state. Today we turn that state into a trading algorithm: a repeatable decision engine that says what it wants to do.

In this course phase, the algorithm should produce a clear signal or order intent, not a fully deployed trading system yet.

A practical flow looks like this:

Market data -> Features -> Signal -> Order intent -> Later: risk/execution

We will focus on the middle: features, signal rules, and clean C++ strategy structure.

From Data To Decisions

Algorithm Shape

A trading algorithm is a stateful function

In ordinary programming, you might think of a function like:

input -> output

But a trading algorithm usually needs memory:

new market event + previous state -> updated state + decision

Examples of strategy state:

  • Last traded price
  • Recent price window
  • Current signal direction
  • Whether enough data has arrived
  • Last decision timestamp

This builds directly on Data Structures and Algorithms for Trading: vectors, rings, maps, and sliding windows become the memory that makes fast decisions possible.

Algorithm Shape

First Signal Rule

Start with a small deterministic rule

Before building complex strategies, make one tiny rule that is easy to inspect:

  • If price rises more than a threshold from the previous trade: BUY
  • If price falls more than a threshold from the previous trade: SELL
  • Otherwise: HOLD

This is not a recommendation for real trading. It is a useful engineering exercise: the same event-driven structure scales to more realistic algorithms.

```cpp
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>

struct Trade {
    long long timestamp_ms;
    double price;
    int quantity;
};

std::string decide(double previousPrice, double currentPrice, double threshold) {
    double change = currentPrice - previousPrice;

    if (change > threshold) return "BUY";
    if (change < -threshold) return "SELL";
    return "HOLD";
}

int main() {
    std::vector<Trade> trades = {
        {1000, 100.00, 50},
        {1001, 100.03, 40},
        {1002, 100.11, 20},
        {1003, 100.05, 70},
        {1004, 99.92, 60}
    };

    const double threshold = 0.05;

    std::cout << std::fixed << std::setprecision(2);
    std::cout << "Threshold strategy demo\n";

    for (size_t i = 1; i < trades.size(); ++i) {
        const Trade& prev = trades[i - 1];
        const Trade& cur = trades[i];

        std::string action = decide(prev.price, cur.price, threshold);

        std::cout << "t=" << cur.timestamp_ms
                  << " prev=" << prev.price
                  << " current=" << cur.price
                  << " action=" << action << '\n';
    }

    return 0;
}
```

Signal Boundary

A strategy module should usually emit an order intent instead of directly sending an order to the exchange because:

Avoid Signal Chatter

Markets are noisy

A naive rule may flip constantly:

BUY -> HOLD -> BUY -> SELL -> HOLD -> SELL

That behavior is called signal chatter. It often happens when the algorithm reacts to tiny changes around a boundary.

Common engineering tools to reduce chatter:

  • Require enough historical samples before deciding
  • Use a rolling average instead of one raw tick
  • Emit only when the signal changes direction
  • Add clear states like Flat, LongBias, and ShortBias

We are still staying inside the algorithm layer. In the next lesson, risk management will decide whether an intent is acceptable.

Avoid Signal Chatter

Moving Average Signal

A classic feature: rolling averages

A moving average crossover is a simple way to convert recent prices into a directional signal:

  • Fast average: reacts quickly
  • Slow average: reacts more slowly
  • Fast crosses above slow: bullish signal
  • Fast crosses below slow: bearish signal

The important C++ idea is not the finance theory. It is the incremental state update: update sums as new prices arrive, instead of recomputing the full average from scratch every tick.

```cpp
#include <iostream>
#include <deque>
#include <vector>
#include <iomanip>
#include <string>

class RollingAverage {
private:
    std::deque<double> window;
    size_t maxSize;
    double sum = 0.0;

public:
    explicit RollingAverage(size_t size) : maxSize(size) {}

    void add(double value) {
        window.push_back(value);
        sum += value;

        if (window.size() > maxSize) {
            sum -= window.front();
            window.pop_front();
        }
    }

    bool ready() const {
        return window.size() == maxSize;
    }

    double value() const {
        return sum / static_cast<double>(window.size());
    }
};

class MovingAverageCrossStrategy {
private:
    RollingAverage fast;
    RollingAverage slow;
    int previousDirection = 0; // -1 bearish, 0 unknown/flat, +1 bullish

public:
    MovingAverageCrossStrategy(size_t fastWindow, size_t slowWindow)
        : fast(fastWindow), slow(slowWindow) {}

    std::string onPrice(double price) {
        fast.add(price);
        slow.add(price);

        if (!fast.ready() || !slow.ready()) {
            return "WAIT";
        }

        int direction = 0;
        if (fast.value() > slow.value()) direction = 1;
        if (fast.value() < slow.value()) direction = -1;

        if (direction != 0 && direction != previousDirection) {
            previousDirection = direction;
            return direction == 1 ? "BUY_INTENT" : "SELL_INTENT";
        }

        return "NO_NEW_SIGNAL";
    }
};

int main() {
    std::vector<double> prices = {
        100.0, 100.1, 100.2, 100.3, 100.4,
        100.2, 100.0, 99.8, 99.6, 99.9, 100.2
    };

    MovingAverageCrossStrategy strategy(3, 5);

    std::cout << std::fixed << std::setprecision(2);
    for (double price : prices) {
        std::cout << "price=" << price
                  << " decision=" << strategy.onPrice(price) << '\n';
    }

    return 0;
}
```

Stateful Or Stateless

True or false: A moving average crossover strategy can be implemented correctly without storing any prior prices, sums, or previous signal direction.

Strategy Interface

Design for replaceable algorithms

A trading application becomes easier to evolve when strategy logic is behind a small interface.

That lets you swap:

  • Threshold strategy
  • Moving average strategy
  • Mean-reversion strategy
  • News or event-driven strategy

without rewriting your market data ingestion code from the previous lesson.

The key design habit: market data enters through one method, and the strategy returns a simple decision object.

```cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MarketSnapshot {
    std::string symbol;
    double lastPrice;
    double previousPrice;
};

struct OrderIntent {
    std::string symbol;
    std::string side;   // BUY, SELL, or NONE
    std::string reason;
};

class Strategy {
public:
    virtual ~Strategy() = default;
    virtual OrderIntent onMarketData(const MarketSnapshot& snapshot) = 0;
};

class MomentumStrategy : public Strategy {
private:
    double threshold;

public:
    explicit MomentumStrategy(double thresholdMove)
        : threshold(thresholdMove) {}

    OrderIntent onMarketData(const MarketSnapshot& snapshot) override {
        double move = snapshot.lastPrice - snapshot.previousPrice;

        if (move > threshold) {
            return {snapshot.symbol, "BUY", "upward momentum"};
        }
        if (move < -threshold) {
            return {snapshot.symbol, "SELL", "downward momentum"};
        }
        return {snapshot.symbol, "NONE", "move too small"};
    }
};

int main() {
    std::unique_ptr<Strategy> strategy =
        std::make_unique<MomentumStrategy>(0.10);

    std::vector<MarketSnapshot> snapshots = {
        {"ABC", 250.05, 250.00},
        {"ABC", 250.22, 250.05},
        {"ABC", 250.08, 250.22}
    };

    for (const auto& snapshot : snapshots) {
        OrderIntent intent = strategy->onMarketData(snapshot);
        std::cout << snapshot.symbol
                  << " last=" << snapshot.lastPrice
                  << " side=" << intent.side
                  << " reason=" << intent.reason << '\n';
    }

    return 0;
}
```

Complete The Term

Complete the sentence:

A strategy should usually return an order ______ rather than directly placing an exchange order.

Algorithm Checklist

What a good first algorithm includes

Before moving forward, your strategy should have these qualities:

  • Deterministic: same input events produce the same decisions
  • Stateful where needed: rolling windows, last signal, last price
  • Inspectable: logs explain why a signal happened
  • Separated: strategy emits intent; it does not own every system concern
  • Fast enough by design: incremental updates instead of repeated full scans

You now have the core decision engine of an algo trading application. Next, we will add Implementing Risk Management Techniques, where those intents are checked before they can become real orders.

Algorithm Checklist

Open interactive view →