-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path06_capstone_engine.cpp
More file actions
197 lines (180 loc) · 8.27 KB
/
Copy path06_capstone_engine.cpp
File metadata and controls
197 lines (180 loc) · 8.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// ============================================================================
// LESSON 06 — Capstone: a mini event-driven trading engine
// ============================================================================
// Build & run (from folder root): make 06 && ./bin/06_capstone_engine
//
// Everything from lessons 01-05 in one place, shaped like a real system:
//
// CSV feed ──> FeedHandler ──ticks──> Engine ──> [BookView] (market state)
// └──> [Strategy]───orders──> [Risk]──> [SimVenue]
//
// The architectural ideas to take away:
// * EVENT-DRIVEN: everything is a reaction to a tick. One thread, one
// ordered stream of events — real engines are single-threaded per
// instrument precisely to avoid locks on the hot path.
// * PRE-TRADE RISK sits BETWEEN strategy and venue and cannot be bypassed.
// This layer is legally mandatory (exchanges require it) and it's always
// built as a separate component so a buggy strategy can't skip it.
// * Components talk through small interfaces, so each is testable alone.
// ============================================================================
#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <deque>
using Price = std::int64_t;
using Qty = std::int64_t;
struct Tick { std::int64_t ts_ns; Price px; Qty qty; char side; };
// --- the seam between components (lesson 04) --------------------------------
class OrderSink {
public:
virtual ~OrderSink() = default;
virtual bool send(char side, Qty qty, Price px) = 0; // false = rejected
};
// --- execution venue (simulated) ---------------------------------------------
class SimVenue : public OrderSink {
public:
bool send(char side, Qty qty, Price px) override {
const std::int64_t sgn = (side == 'B') ? 1 : -1;
pos_ += sgn * qty;
cash_ -= sgn * qty * (px + sgn * SLIPPAGE); // pay a realistic cost:
++fills_; // cross half the spread
return true;
}
double pnl(Price mark) const { return double(cash_ + pos_ * mark) / 10'000.0; }
Qty pos() const { return pos_; }
int fills() const { return fills_; }
private:
static constexpr Price SLIPPAGE = 25; // 0.25 cents worse than mid
Qty pos_ = 0; std::int64_t cash_ = 0; int fills_ = 0;
};
// --- pre-trade risk gate ------------------------------------------------------
// Wraps the real venue (same interface — it IS an OrderSink) and enforces
// hard limits. The "decorator" pattern: strategy -> risk -> venue.
class RiskGate : public OrderSink {
public:
RiskGate(OrderSink& venue, Qty max_pos, int max_orders)
: venue_(venue), max_pos_(max_pos), max_orders_(max_orders) {}
bool send(char side, Qty qty, Price px) override {
const Qty next = pos_ + ((side == 'B') ? qty : -qty);
if (orders_ >= max_orders_) return reject("order-count limit");
if (next > max_pos_ || next < -max_pos_) return reject("position limit");
if (qty <= 0 || px <= 0) return reject("malformed order");
++orders_; pos_ = next;
return venue_.send(side, qty, px);
}
int rejected() const { return rejected_; }
private:
bool reject(const char* why) {
++rejected_;
std::cout << " [RISK] REJECT: " << why << "\n";
return false;
}
OrderSink& venue_;
Qty max_pos_; Qty pos_ = 0;
int max_orders_; int orders_ = 0; int rejected_ = 0;
};
// --- strategy: mean reversion this time ---------------------------------------
// Price far below its rolling mean -> buy the dip; far above -> sell.
// Opposite family to lesson 04's momentum strategy — run both on the same
// data and note they make money in opposite regimes.
class MeanRevertStrategy {
public:
MeanRevertStrategy(OrderSink& sink, std::size_t window, Price band)
: sink_(sink), window_(window), band_(band) {}
void on_tick(const Tick& t) {
buf_.push_back(t.px); sum_ += t.px;
if (buf_.size() > window_) { sum_ -= buf_.front(); buf_.pop_front(); }
if (buf_.size() < window_) return;
const Price mean = static_cast<Price>(sum_ / static_cast<std::int64_t>(window_));
if (t.px < mean - band_ && pos_ <= 0) { // cheap -> long
if (sink_.send('B', LOT, t.px)) pos_ += LOT;
} else if (t.px > mean + band_ && pos_ >= 0) { // rich -> short
if (sink_.send('S', LOT, t.px)) pos_ -= LOT;
}
// Note: pos_ only changes if the order was ACCEPTED. Keeping your
// internal state in sync with what actually happened downstream is
// half of all real trading-system bugs.
}
void flatten(Price px) {
if (pos_ > 0) sink_.send('S', pos_, px);
else if (pos_ < 0) sink_.send('B', -pos_, px);
pos_ = 0;
}
private:
static constexpr Qty LOT = 100;
OrderSink& sink_;
std::size_t window_;
Price band_;
std::deque<Price> buf_;
std::int64_t sum_ = 0;
Qty pos_ = 0;
};
// --- feed handler --------------------------------------------------------------
class FeedHandler {
public:
explicit FeedHandler(const std::string& path) : file_(path) {}
bool ok() const { return static_cast<bool>(file_); }
// std::optional-style: returns false when the feed is exhausted.
bool next(Tick& out) {
std::string line;
while (std::getline(file_, line)) {
if (parse(line, out)) return true; // skip bad rows silently-ish
}
return false;
}
private:
static bool parse(const std::string& line, Tick& t) {
std::istringstream ss(line);
std::string f;
try {
if (!std::getline(ss, f, ',')) return false; t.ts_ns = std::stoll(f);
if (!std::getline(ss, f, ',')) return false; // symbol (ignored here)
if (!std::getline(ss, f, ',')) return false; t.px = std::stoll(f);
if (!std::getline(ss, f, ',')) return false; t.qty = std::stoll(f);
if (!std::getline(ss, f, ',')) return false; t.side = f.empty() ? '?' : f[0];
} catch (const std::exception&) { return false; } // stoll on garbage throws
return t.px > 0 && t.qty > 0;
}
std::ifstream file_;
};
// --------------------------------------------------------------------------------
int main() {
std::cout << "=== 06 Capstone engine (mean reversion, risk-gated) ===\n";
FeedHandler feed("data/ticks.csv");
if (!feed.ok()) { std::cerr << "run from folder root\n"; return 1; }
SimVenue venue;
RiskGate risk(venue, /*max_pos=*/300, /*max_orders=*/50);
MeanRevertStrategy strat(risk, /*window=*/20, /*band=*/150); // 1.5 cents
// The event loop. This IS the engine — everything else is plumbing.
Tick t{};
std::size_t events = 0;
Price last = 0;
while (feed.next(t)) {
strat.on_tick(t);
last = t.px;
++events;
}
strat.flatten(last);
std::cout << "----------------------------------------\n"
<< "events processed : " << events << "\n"
<< "fills : " << venue.fills() << "\n"
<< "risk rejections : " << risk.rejected() << "\n"
<< "end position : " << venue.pos() << "\n"
<< "P&L : $" << venue.pnl(last) << "\n";
return 0;
}
// ----------------------------------------------------------------------------
// EXERCISES (the graduation projects)
// 1. Tighten max_pos to 100 and watch the risk gate start rejecting; confirm
// the strategy's internal position stays consistent anyway.
// 2. Feed the ticks into lesson 03's OrderBook as they arrive (treat each
// tick as a small add) and have the strategy quote around ITS mid instead
// of the trade price. You've now built a toy market maker.
// 3. Time the whole run with lesson 05's technique: ns per event, end to end.
// Then find the slowest line. (Spoiler: it's the string parsing — which
// is why real feeds are binary and lesson 05 exists.)
// 4. Split this file into engine.hpp / strategies.hpp / main.cpp and make
// the Makefile build it — your first multi-file C++ project.
// ----------------------------------------------------------------------------