12
12
#include " matplotlibcpp.h"
13
13
#include < Python.h>
14
14
15
+ // Declare global namespaces to simplify calling objects
15
16
using namespace web ;
16
17
using namespace web ::websockets::client;
17
18
using namespace boost ::property_tree;
18
19
namespace plt = matplotlibcpp;
19
20
21
+ // Coinbase datafeed class
20
22
class datafeed {
21
23
private:
24
+ // Converts a string response to JSON using Boost
22
25
ptree JSON (std::string message){
23
26
std::stringstream ss (message);
24
27
ptree result;
25
28
read_json (ss, result);
26
29
return result;
27
30
}
28
31
32
+ // Parses the level2 orderbook for Bitcoin and updates each change in order which the book receives
29
33
void CYCLONE (ptree df, std::map<double , double > & bids, std::map<double , double > & asks){
30
34
bool snapshot = false ;
31
35
bool l2update = false ;
@@ -37,9 +41,11 @@ class datafeed {
37
41
hold.push_back (kt->second .get_value <std::string>().c_str ());
38
42
}
39
43
44
+ // Extract the latest price and volume
40
45
double price = atof (hold[1 ].c_str ());
41
46
double volume = atof (hold[2 ].c_str ());
42
47
48
+ // Erases part of book if the volume equals zero meaning that the order has been filled or cancelled fully
43
49
if (hold[0 ] == " buy" ){
44
50
if (volume == 0 ){
45
51
bids.erase (price);
@@ -55,6 +61,7 @@ class datafeed {
55
61
}
56
62
}
57
63
}
64
+ // Parses the bid book in the initial snapshot
58
65
if (snapshot == true && it->first == " bids" ){
59
66
for (ptree::const_iterator jt = it->second .begin (); jt != it->second .end (); ++jt){
60
67
std::vector<double > hold;
@@ -64,6 +71,7 @@ class datafeed {
64
71
bids[hold[0 ]] = hold[1 ];
65
72
}
66
73
}
74
+ // Parses the ask book in the initial snapshot
67
75
if (snapshot == true && it->first == " asks" ){
68
76
for (ptree::const_iterator jt = it->second .begin (); jt != it->second .end (); ++jt){
69
77
std::vector<double > hold;
@@ -74,6 +82,7 @@ class datafeed {
74
82
}
75
83
}
76
84
85
+ // Activates whether the message is a snapshot or an update
77
86
if (it->first == " type" ){
78
87
if (it->second .get_value <std::string>() == " l2update" ){
79
88
l2update = true ;
@@ -88,11 +97,13 @@ class datafeed {
88
97
89
98
90
99
public:
91
-
100
+
101
+ // Level2 limit orderbook websocket feed using cpprest
92
102
static void Socket (datafeed dx, std::map<double , double > & bids, std::map<double , double > & asks){
93
103
std::string url = " wss://ws-feed.exchange.coinbase.com" ;
94
104
std::string msg = " {\" type\" :\" subscribe\" ,\" product_ids\" :[\" BTC-USD\" ],\" channels\" :[\" level2_batch\" ]}" ;
95
105
106
+ // Connect to client and send the subscription message
96
107
websocket_client client;
97
108
client.connect (url).wait ();
98
109
websocket_outgoing_message outmsg;
@@ -103,6 +114,7 @@ class datafeed {
103
114
client.receive ().then ([](websocket_incoming_message inmsg){
104
115
return inmsg.extract_string ();
105
116
}).then ([&](std::string message){
117
+ // Retreives response from websocket and parses the data as a snapshot of the book, or an update of the book
106
118
dx.CYCLONE (dx.JSON (message), std::ref (bids), std::ref (asks));
107
119
}).wait ();
108
120
}
@@ -112,42 +124,50 @@ class datafeed {
112
124
}
113
125
};
114
126
127
+ // Sets the examination depth to 80 and pulls the orderbooks price and volume for both bids and asks
115
128
std::map<std::string, std::vector<double >> Extract (std::map<double , double > bids, std::map<double , double > asks)
116
129
{
130
+ // Set depth of book
117
131
int depth = 80 ;
118
132
std::map<std::string, std::vector<double >> result;
119
133
134
+ // Pulls bid orders and takes the cumulative volume summation
120
135
int count = 0 ;
121
136
double bidvol = 0 ;
122
137
for (auto it = bids.rbegin (); it != bids.rend (); ++it){
123
138
bidvol += it->second ;
124
139
result[" bidPrice" ].push_back (it->first );
125
140
result[" bidSize" ].push_back (bidvol);
126
141
count += 1 ;
142
+ // Breaks loop once depth limit has been reached
127
143
if (count >= depth){
128
144
break ;
129
145
}
130
146
}
131
147
148
+ // Pulls ask orders and takes the cumulative volume summation
132
149
count = 0 ;
133
150
double askvol = 0 ;
134
151
for (auto it = asks.begin (); it != asks.end (); ++it){
135
152
askvol += it->second ;
136
153
result[" askPrice" ].push_back (it->first );
137
154
result[" askSize" ].push_back (askvol);
138
155
count += 1 ;
156
+ // Breaks loop once depth limit has been reached
139
157
if (count >= depth){
140
158
break ;
141
159
}
142
160
}
143
161
162
+ // Bids must be reveresed in order to be plotted as the lowest bid must be at the beginning of the vector
144
163
std::reverse (result[" bidPrice" ].begin (), result[" bidPrice" ].end ());
145
164
std::reverse (result[" bidSize" ].begin (), result[" bidSize" ].end ());
146
165
147
166
148
167
return result;
149
168
}
150
169
170
+ // Builds a vector with a single value for n elements
151
171
std::vector<double > push_into (double ii, int n){
152
172
std::vector<double > result;
153
173
for (int i = 0 ; i < n; ++i){
@@ -158,15 +178,18 @@ std::vector<double> push_into(double ii, int n){
158
178
159
179
int main ()
160
180
{
181
+ // Declare two plots, one for bid and one for ask
161
182
PyObject * ax = plt::chart (121 );
162
183
PyObject * ay = plt::chart (122 );
163
184
164
185
std::map<double , double > bids, asks;
165
186
std::map<std::string, std::vector<double >> preprice;
166
187
188
+ // Open datafeed thread
167
189
datafeed wsfeed;
168
190
std::thread feed (wsfeed.Socket , wsfeed, std::ref (bids), std::ref (asks));
169
191
192
+ // Sleep for 10 seconds to allow the orderbook thread to build a dataset
170
193
std::this_thread::sleep_for (std::chrono::seconds (10 ));
171
194
172
195
std::vector<std::vector<double >> bX, bY, bZ, aX, aY, aZ;
@@ -176,7 +199,10 @@ int main()
176
199
int limit = 80 ;
177
200
178
201
while (true ){
202
+ // Extract the depth orderbook data
179
203
preprice = Extract (bids, asks);
204
+
205
+ // Store bid snapshots for 3D plotting
180
206
bX.clear ();
181
207
bY.clear ();
182
208
bZ.push_back (preprice[" bidSize" ]);
@@ -186,6 +212,7 @@ int main()
186
212
ii += 1 ;
187
213
}
188
214
215
+ // Store ask snapshots for 3D plotting
189
216
aX.clear ();
190
217
aY.clear ();
191
218
aZ.push_back (preprice[" askSize" ]);
@@ -195,7 +222,7 @@ int main()
195
222
jj += 1 ;
196
223
}
197
224
198
-
225
+ // Erase data from the beginning of the vectors if limit has been reached
199
226
if (bZ.size () >= limit){
200
227
bX.erase (bX.begin ());
201
228
bY.erase (bY.begin ());
@@ -208,6 +235,7 @@ int main()
208
235
aZ.erase (aZ.begin ());
209
236
}
210
237
238
+ // Clear and plot the two orderbooks in their own plots
211
239
plt::Clear3DChart (ax);
212
240
plt::Clear3DChart (ay);
213
241
@@ -222,4 +250,4 @@ int main()
222
250
plt::show ();
223
251
feed.join ();
224
252
return 0 ;
225
- }
253
+ }
0 commit comments