Crypto backtesting
Loading...
Searching...
No Matches
trade.h
Go to the documentation of this file.
1#pragma once
2
3#include "constants.h"
4#include "core.h"
5#include "ohlc.h"
6#include <cassert>
7#include <ranges>
8#include <vector>
9
10namespace fx {
11
12using namespace std::views;
13
14/// Calculate average of series, applying a function to each element (which
15/// could be a no-op)
16constexpr auto to_average_func = [](auto &&xs, auto func) {
17 assert(not std::ranges::empty(xs));
18 return to_sum(xs | transform(func)) / to_size(xs);
19};
20
21constexpr auto to_average_func2 = [](auto &&xs, auto &&func) {
22 assert(not std::ranges::empty(xs));
23
24 auto &&sum =
25 std::ranges::fold_left(xs | transform(func), 0.0,
26 [](const auto acc, auto x) { return acc + x; });
27
28 return sum;
29};
30
31/// Calculate spot value, note we don't average all of the OHLC prices
32constexpr auto to_spot = [](auto &&xs) {
33 // Don't include the close price in the average
34 assert(to_size(xs) > 4);
35
36 auto &&spot = to_average_func(xs | drop(1) | take(3), identity);
37 return spot;
38};
39
40static_assert(to_spot(std::vector{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0}) == 2.0);
41
42/// Calculate ATR of a series
43constexpr auto to_atr(auto &&series) {
44 // Get all adjacent pairs
45 auto pairs = series | slide(2);
46
47 // Calculate the true price and volume
48 auto true_ranges = pairs | transform([](auto px) {
49 assert(to_size(px) == 2);
50 auto a = px[0];
51 auto b = px[1];
52 auto tr_high = std::abs(to_high(b) - to_close(a));
53 auto tr_low = std::abs(to_low(b) - to_close(a));
54 return (tr_high + tr_low) / 2.0;
55 });
56
57 // Calculate average true range
58 return to_average_func(true_ranges, identity);
59}
60
61/// Calculate ATR of a series
62constexpr auto to_atr2(auto &&series) {
63 // Get all adjacent pairs
64 auto pairs = series | slide(2);
65
66 // Calculate the true price and volume
67 auto true_ranges = pairs | transform([](auto &&px) {
68 assert(to_size(px) == 2);
69 auto &&a = px[0];
70 auto &&b = px[1];
71 auto &&tr_high = std::abs(to_high(b) - to_close(a));
72 auto &&tr_low = std::abs(to_low(b) - to_close(a));
73 return (tr_high + tr_low) / 2.0;
74 });
75
76 // Calculate average true range
77 return to_average_func(true_ranges, identity);
78}
79
80/// Calculate ATR of a series
81constexpr double to_atr3(auto &&series) {
82 // Get all adjacent pairs
83 auto &&pairs = series | slide(2);
84
85 // Calculate the true price and volume
86 auto &&true_ranges = pairs | transform([](auto &&px) {
87 assert(to_size(px) == 2);
88 auto &&a = px[0];
89 auto &&b = px[1];
90 auto &&tr_high = std::abs(to_high(b) - to_close(a));
91 auto &&tr_low = std::abs(to_low(b) - to_close(a));
92 return (tr_high + tr_low) / double{2.0f};
93 });
94
95 // Calculate average true range
96 return to_average_func(true_ranges, [](auto &&x) { return x; });
97}
98
99/// Calculate average volume over a series
100constexpr auto to_average_volume = [](auto series) {
101 return to_average_func(series, to_volume);
102};
103
104/// Calculate average volume over a series
105constexpr auto to_average_volume2 = [](auto &&series) {
106 return to_average_func2(series, to_volume);
107};
108
109/// Calculate VWAP rolling average
110constexpr double to_vwap(std::ranges::range auto &&xs) {
111 // Initialise cumulative volumes
112 auto cumulative_tp_volume = 0.0;
113 auto cumulative_volume = 0.0;
114
115 // Calculate VWAP for each sliding window
116 for (auto &&x : xs) {
117 cumulative_tp_volume += to_spot(x) * to_volume(x);
118 cumulative_volume += to_volume(x);
119 }
120
121 return cumulative_tp_volume / cumulative_volume;
122}
123
124/// Calculate VWAP rolling average
125constexpr double to_vwap2(std::ranges::range auto &&xs) {
126 // Initialise cumulative volumes
127 auto &&cumulative_tp_volume = 0.0;
128 auto &&cumulative_volume = 0.0;
129
130 // Calculate VWAP for each sliding window
131 for (auto &&x : xs) {
132 auto &&vol = to_volume(x);
133 cumulative_tp_volume += to_spot(x) * vol;
134 cumulative_volume += vol;
135 }
136
137 return cumulative_tp_volume / cumulative_volume;
138}
139
140/// Test if there has been a recent minimum
141auto is_recent_dip2(auto &&series) {
142 // Find minimum spot value
143 auto &&min_it = std::ranges::min_element(
144 series, [](auto &&a, auto &&b) { return to_spot(a) < to_spot(b); });
145
146 // Return true if the minimum is in the middle
147 return std::ranges::distance(std::ranges::begin(series), min_it) ==
148 to_size<ssize_t>(series) / 2;
149}
150
151/// Find an exit in a series
152auto to_exit = [](auto &&series) {
153 // Calculate the exit thresholds
154 const double open_price = to_spot(to_first(series));
155 const double take_price = open_price * (100.0 + take_profit) / 100.0;
156 const double stop_price = open_price * (100.0 - stop_loss) / 100.0;
157
158 // Drop everything up to the exit
159 auto to_trunc =
160 series | drop_while([=](auto &&xs) {
161 const double close_price = to_spot(xs);
162 return close_price > stop_price and close_price < take_price;
163 }) |
164 common;
165
166 auto truncated = std::vector(std::begin(to_trunc), std::end(to_trunc));
167
168 // If it's been truncated to nothing, then return the last price
169 if (std::ranges::empty(truncated))
170 return to_last(series);
171
172 // Otherwise, the first price in the truncated series is the exit
173 return to_first(truncated);
174};
175
176/// Find an exit in a series
177auto to_exit2 = [](auto &&series) {
178 // Calculate the exit thresholds
179 auto &&open_price = to_spot(to_first(series));
180 auto &&take_price = open_price * (100.0 + take_profit) / 100.0;
181 auto &&stop_price = open_price * (100.0 - stop_loss) / 100.0;
182
183 // Drop everything up to the exit
184 auto &&to_trunc =
185 series | drop_while([&](auto &&xs) {
186 auto &&close_price = to_spot(xs);
187 return close_price > stop_price and close_price < take_price;
188 }) |
189 common;
190
191 auto &&truncated = std::vector(std::begin(to_trunc), std::end(to_trunc));
192
193 // If it's been truncated to nothing, then return the last price
194 // Otherwise, the first price in the truncated series is the exit
195 return std::ranges::empty(truncated) ? to_last(series) : to_first(truncated);
196};
197
198/// Calculate if the final price is an entry
199auto is_entry = [](std::ranges::range auto &&series) {
200 // Create subsets of complete series
201 const auto last_half =
202 series | drop(std::llround(to_size<double>(series) / 2.0));
203 const auto last_quarter =
204 series | drop(std::llround(to_size<double>(series) * 3.0 / 4.0));
205
206 // Copy the final price: the entry point
207 const auto entry = to_last(series);
208
209 // Test all the criteria for a trade
210 return
211 // No small change
213
214 // Above minimum ATR
215 and (100.0 * to_atr3(series) / to_spot(entry)) > fx::minimum_atr
216
217 // Average volume is used to determine if the last price is a spike
218 and
219 to_volume(entry) > to_average_volume(series | take(to_size(series) - 1))
220
221 // There has been a recent minimum
222 and is_recent_dip2(last_quarter)
223
224 // Price is below the long VWAP
225 and to_spot(entry) < to_vwap(series)
226
227 // Short VWAP is above the long VWAP
228 and to_vwap(last_half) > to_vwap(series);
229};
230
231/// Calculate if the final price is an entry
232auto is_entry2 = [](std::ranges::range auto &&series) {
233 assert(not std::ranges::empty(series));
234
235 // Create subsets of complete series
236 // Using reverse simplifies the drop logic
237 auto &&last_half = series | reverse | take(fx::win2) | reverse;
238 auto &&last_quarter = series | reverse | take(fx::win4) | reverse;
239
240 // Copy the final price: the entry point
241 auto &&entry = to_last(series);
242
243 // Test all the criteria for a trade
244 return
245
246 // No old trades
248
249 // No small change
250 and to_spot(entry) > fx::minimum_entry
251
252 // Above minimum ATR
253 and (100.0 * to_atr3(series) / to_spot(entry)) > fx::minimum_atr
254
255 // Average volume is used to determine if the last price is a spike
256 and to_volume(entry) >
257 to_average_volume(series | reverse | drop(1uz) | reverse)
258
259 // There has been a recent minimum
260 and is_recent_dip2(last_quarter)
261
262 // Price is below the long VWAP
263 and to_spot(entry) < to_vwap(series)
264
265 // Short VWAP is above the long VWAP
266 and to_vwap(last_half) > to_vwap(series);
267};
268
269} // namespace fx
Core routines that don't depend on any other fx routines.
Definition constants.h:3
constexpr auto to_atr(auto &&series)
Calculate ATR of a series.
Definition trade.h:43
constexpr auto to_first(std::ranges::range auto &&xs)
Return the first entry in a series.
Definition core.h:19
constexpr double to_vwap(std::ranges::range auto &&xs)
Calculate VWAP rolling average.
Definition trade.h:110
constexpr auto to_time(std::ranges::range auto &&xs)
Get time of a data point.
Definition ohlc.h:9
constexpr auto stop_loss
Close position if price has decreased by this percentage.
Definition constants.h:14
constexpr auto to_average_func
Definition trade.h:16
auto to_exit2
Find an exit in a series.
Definition trade.h:177
constexpr auto to_atr2(auto &&series)
Calculate ATR of a series.
Definition trade.h:62
constexpr auto to_last
Return the last entry in a series.
Definition core.h:25
constexpr double to_vwap2(std::ranges::range auto &&xs)
Calculate VWAP rolling average.
Definition trade.h:125
constexpr auto to_low(std::ranges::range auto &&xs)
Get low price for a data point.
Definition ohlc.h:26
auto to_exit
Find an exit in a series.
Definition trade.h:152
constexpr auto minimum_entry
Minimum price to consider a trade.
Definition constants.h:17
constexpr auto take_profit
Close position if price has increased by this percentage.
Definition constants.h:11
constexpr double to_atr3(auto &&series)
Calculate ATR of a series.
Definition trade.h:81
constexpr auto to_average_volume
Calculate average volume over a series.
Definition trade.h:100
constexpr auto earliest_entry_epoch
Furthest back in time to consider a trade.
Definition constants.h:26
auto is_recent_dip2(auto &&series)
Test if there has been a recent minimum.
Definition trade.h:141
constexpr auto to_average_func2
Definition trade.h:21
constexpr auto win4
Definition constants.h:8
auto is_entry2
Calculate if the final price is an entry.
Definition trade.h:232
constexpr auto to_sum(std::ranges::range auto &&xs)
Calculate sum of series.
Definition core.h:31
constexpr auto identity
Just passing through.
Definition core.h:51
constexpr auto minimum_atr
Minimum (normalised) ATR to consider a trade.
Definition constants.h:20
constexpr auto to_close(std::ranges::range auto &&xs)
Get close price for a data point.
Definition ohlc.h:32
constexpr auto win2
Definition constants.h:7
constexpr auto to_volume
Get total volume for a data point.
Definition ohlc.h:38
constexpr auto to_spot
Calculate spot value, note we don't average all of the OHLC prices.
Definition trade.h:32
constexpr auto to_high(std::ranges::range auto &&xs)
Get high price for a data point.
Definition ohlc.h:20
constexpr T to_size(std::ranges::range auto &&xs)
Calculate size of a series, return type depends on input param.
Definition core.h:14
constexpr auto to_average_volume2
Calculate average volume over a series.
Definition trade.h:105
auto is_entry
Calculate if the final price is an entry.
Definition trade.h:199