algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
reversion.hpp
1#pragma once
2
3#include "./signal.hpp"
4
5#include "../event.hpp"
6#include "../intent.hpp"
7
8#include <algotradelib/analytics>
9#include <algotradelib/core>
10
11#include <cstddef>
12#include <string>
13
14namespace algotradelib {
15namespace strategy {
16namespace vanilla {
17
27 struct Params {
31 std::size_t window_{20};
39 std::string tag_{"zscore-reversion"};
40 };
41
44
49 std::size_t warmup_bars() const { return params_.window_ + 1; }
50
57 template <class Ctx>
58 void on_bar(Ctx& ctx, const BarEvent& bar) {
59 if (!(bar.instrument_ == params_.instrument_)) {
60 return;
61 }
62 if (params_.window_ < 2 || !(params_.entry_ > 0) || !(params_.exit_ > 0) ||
64 // An entry that is not below the mean, an exit that is not, or an exit at or beyond the entry
65 // is contradictory; do nothing.
66 return;
67 }
68 const auto* series = ctx.bars(bar.instrument_, bar.interval_);
69 if (series == nullptr) {
70 return;
71 }
72 const auto closes = series->column(analytics::BarField::CLOSE);
73 const auto window = trailing(closes, params_.window_);
74 if (!window) {
75 return;
76 }
77 const auto scores = analytics::zscore(*window, params_.window_);
78 const auto score = scores[scores.size() - 1];
79 // Unobserved covers both the warm-up and a window with no dispersion, where a score does not
80 // exist. Either way there is nothing to act on.
81 if (!math::observed(score)) {
82 return;
83 }
84
85 // Net across every position row: a spent lot of 0 beside a live one is not flat.
86 const auto held = ctx.portfolio().net_quantity_of(params_.instrument_);
87 if (!held) {
88 // Rows that do not add up say nothing about what is held; reading them as flat would enter.
89 return;
90 }
91 const bool holding = held->coefficient() != 0;
92
93 TargetPosition target;
95 target.tag_ = params_.tag_;
96 if (!holding && score <= -params_.entry_) {
98 } else if (holding && -params_.exit_ <= score) {
100 } else {
101 return;
102 }
103 ctx.submit(target);
104 }
105};
106
124 struct Params {
128 std::size_t period_{14};
136 std::string tag_{"rsi-threshold"};
137 };
138
141
146 std::size_t warmup_bars() const { return params_.period_ + 1; }
147
154 template <class Ctx>
155 void on_bar(Ctx& ctx, const BarEvent& bar) {
156 if (!(bar.instrument_ == params_.instrument_)) {
157 return;
158 }
160 return;
161 }
162 const auto* series = ctx.bars(bar.instrument_, bar.interval_);
163 if (series == nullptr) {
164 return;
165 }
166 const auto closes = series->column(analytics::BarField::CLOSE);
167 if (static_cast<std::size_t>(closes.size()) < warmup_bars()) {
168 return;
169 }
170 // The whole cache, not a slice: Wilder's smoothing depends on its seed.
171 const auto index = analytics::rsi(closes, params_.period_);
172 const auto level = index[index.size() - 1];
173 if (!math::observed(level)) {
174 return;
175 }
176
177 // Net across every position row: a spent lot of 0 beside a live one is not flat.
178 const auto held = ctx.portfolio().net_quantity_of(params_.instrument_);
179 if (!held) {
180 // Rows that do not add up say nothing about what is held; reading them as flat would enter.
181 return;
182 }
183 const bool holding = held->coefficient() != 0;
184
185 TargetPosition target;
187 target.tag_ = params_.tag_;
188 if (!holding && level <= params_.oversold_) {
190 } else if (holding && params_.overbought_ <= level) {
192 } else {
193 return;
194 }
195 ctx.submit(target);
196 }
197};
198
208 struct Params {
212 std::size_t lookback_{20};
216 std::string tag_{"donchian-breakout"};
217 };
218
221
226 std::size_t warmup_bars() const { return params_.lookback_ + 1; }
227
234 template <class Ctx>
235 void on_bar(Ctx& ctx, const BarEvent& bar) {
236 if (!(bar.instrument_ == params_.instrument_)) {
237 return;
238 }
239 if (params_.lookback_ == 0) {
240 return;
241 }
242 const auto* series = ctx.bars(bar.instrument_, bar.interval_);
243 if (series == nullptr) {
244 return;
245 }
246 const auto closes = series->column(analytics::BarField::CLOSE);
247 const auto window = trailing(closes, params_.lookback_);
248 if (!window) {
249 return;
250 }
251
252 // The channel excludes the latest row, so a break is measurable at all.
253 const auto prior = window->head(window->size() - 1);
254 const auto highest = analytics::rolling_max(prior, params_.lookback_);
255 const auto lowest = analytics::rolling_min(prior, params_.lookback_);
256 const auto ceiling = highest[highest.size() - 1];
257 const auto floor = lowest[lowest.size() - 1];
258 const auto close = (*window)[window->size() - 1];
259 if (!math::observed(ceiling) || !math::observed(floor) || !math::observed(close)) {
260 return;
261 }
262
263 TargetPosition target;
265 target.tag_ = params_.tag_;
266 if (ceiling < close) {
268 } else if (close < floor) {
270 } else {
271 return;
272 }
273 ctx.submit(target);
274 }
275};
276
277} // namespace vanilla
278} // namespace strategy
279} // namespace algotradelib
An exact signed decimal: an int64 coefficient times 10^-scale.
Definition types.hpp:297
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
Definition types.hpp:323
The events a runner delivers to a strategy's handlers, and the market phase.
What a strategy asks the runner for: orders (OrderIntent), target positions (TargetPosition) and canc...
math::Vector rolling_min(math::SeriesView values, std::size_t window, std::size_t min_observed)
Computes the trailing minimum of the observed values.
Definition rolling.hpp:252
math::Vector rsi(math::SeriesView prices, std::size_t period=14)
Computes Wilder's relative strength index.
math::Vector zscore(math::SeriesView values, std::size_t window, std::size_t min_observed)
Computes how many trailing sample standard deviations each value lies from its trailing mean.
math::Vector rolling_max(math::SeriesView values, std::size_t window, std::size_t min_observed)
Computes the trailing maximum of the observed values.
Definition rolling.hpp:295
double Scalar
The floating-point type the analytics and math layers compute in (double).
Definition scalar.hpp:18
bool observed(Scalar value)
Tests whether a value is an observation.
Definition scalar.hpp:36
The strategies this library ships (buy and hold, crossovers, mean reversion, sizing,...
std::optional< math::SeriesView > trailing(math::SeriesView values, std::size_t count)
Returns the last count + 1 rows of a series.
Definition signal.hpp:145
What a strategy sees and emits: events, intents, the portfolio, reconciliation and risk limits.
Definition concept.hpp:16
The library: broker-neutral types (core), and the modules below.
Helpers for the vanilla strategies that compute indicators.
Identifies an instrument: its country, venue and symbol.
Definition types.hpp:78
One bar, delivered to on_bar.
Definition event.hpp:202
The position a strategy wants to hold in one instrument.
Definition intent.hpp:442
InstrumentId instrument_
The instrument.
Definition intent.hpp:444
std::string tag_
A label carried to the orders and their fills.
Definition intent.hpp:450
Decimal64 target_quantity_
The position to hold, signed: negative is short, and zero means flat.
Definition intent.hpp:446
std::size_t lookback_
The number of prior closes the channel spans; zero disables the strategy.
InstrumentId instrument_
The instrument to trade; bars for any other instrument are ignored.
Decimal64 quantity_
The shares to hold while in a position.
Donchian channel breakout: holds after the close breaks above the channel, exits after it breaks belo...
void on_bar(Ctx &ctx, const BarEvent &bar)
States a target position for the instrument when its signal enters or exits.
std::size_t warmup_bars() const
Returns how many bars to load before the first live bar.
Decimal64 quantity_
The shares to hold while in a position.
math::Scalar overbought_
Sell at or above this level; must exceed oversold_, or nothing is traded.
std::size_t period_
The RSI period; zero disables the strategy.
math::Scalar oversold_
Buy at or below this level.
InstrumentId instrument_
The instrument to trade; bars for any other instrument are ignored.
Long-only RSI strategy: buys when the index is oversold, sells when it is overbought.
void on_bar(Ctx &ctx, const BarEvent &bar)
States a target position for the instrument when its signal enters or exits.
std::size_t warmup_bars() const
Returns how many bars to load before the first live bar.
InstrumentId instrument_
The instrument to trade; bars for any other instrument are ignored.
Definition reversion.hpp:29
std::size_t window_
The number of closes the z-score is measured over; below 2, nothing is traded.
Definition reversion.hpp:31
math::Scalar exit_
The z-score -exit_ at which to sell; positive and less than entry_, or nothing is traded.
Definition reversion.hpp:35
math::Scalar entry_
How many standard deviations below the mean to buy at; positive, or nothing is traded.
Definition reversion.hpp:33
Decimal64 quantity_
The shares to hold while in a position.
Definition reversion.hpp:37
Long-only mean reversion on the z-score of the close.
Definition reversion.hpp:25
void on_bar(Ctx &ctx, const BarEvent &bar)
States a target position for the instrument when its signal enters or exits.
Definition reversion.hpp:58
std::size_t warmup_bars() const
Returns how many bars to load before the first live bar.
Definition reversion.hpp:49