-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfix_application.cpp
482 lines (443 loc) · 20.2 KB
/
fix_application.cpp
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
#include "fix_application.h"
// Returns SessionID - MarketData for md = true, Trading fr md = false
SessionID FixApplication::sessionID(bool md)
{
for (auto session : sessions)
{
// for FXCM MarketData sessions begin with MD_
if ((session.toString().find("MD_") != string::npos) == md)
{
return session;
}
}
return SessionID();
}
FixApplication::FixApplication()
{
// Initialize unsigned int requestID to 1. We will use this as a
// counter for making request IDs
requestID = 1;
}
// Gets called when quickfix creates a new session. A session comes into and remains in existence
// for the life of the application.
void FixApplication::onCreate(const SessionID& session_ID)
{
// FIX Session created. We must now logon. QuickFIX will automatically send
// the Logon(A) message
cout << "Session -> created" << session_ID << endl;
sessions.push_back(session_ID);
}
// Notifies you when a valid logon has been established with FXCM.
void FixApplication::onLogon(const SessionID& session_ID)
{
// Session logon successful. Now we request TradingSessionStatus which is
// used to determine market status (open or closed), to get a list of securities,
// and to obtain important FXCM system parameters
cout << "Session -> logon" << session_ID << endl;
GetTradingStatus();
}
// Notifies you when an FIX session is no longer online. This could happen during a normal logout
// exchange or because of a forced termination or a loss of network connection.
void FixApplication::onLogout(const SessionID& session_ID)
{
// Session logout
cout << "Session -> logout" << session_ID << endl;
}
// Provides you with a peak at the administrative messages that are being sent from your FIX engine
// to FXCM.
void FixApplication::toAdmin(Message& message, const SessionID& session_ID)
{
// If the Admin message being sent to FXCM is of typle Logon (A), we want
// to set the Username and Password fields. We want to catch this message as it
// is going out.
string msg_type = message.getHeader().getField(FIELD::MsgType);
if(msg_type == "A"){
// Get both username and password from our settings file. Then set these
// respective fields
string user = settings->get().getString("Username");
string pass = settings->get().getString("Password");
message.setField(Username(user));
message.setField(Password(pass));
}
// All messages sent to FXCM must contain the TargetSubID field (both Administrative and
// Application messages). Here we set this.
string sub_ID = settings->get(session_ID).getString("TargetSubID");
message.getHeader().setField(TargetSubID(sub_ID));
}
// A callback for application messages that you are being sent to a counterparty.
void FixApplication::toApp(Message& message, const SessionID& session_ID)
{
// All messages sent to FXCM must contain the TargetSubID field (both Administrative and
// Application messages). Here we set this.
string sub_ID = settings->get(session_ID).getString("TargetSubID");
message.getHeader().setField(TargetSubID(sub_ID));
}
// Notifies you when an administrative message is sent from FXCM to your FIX engine.
void FixApplication::fromAdmin(const Message& message, const SessionID& session_ID)
{
// Call MessageCracker.crack method to handle the message by one of our
// overloaded onMessage methods below
crack(message, session_ID);
}
// One of the core entry points for your FIX application. Every application level request will come through here.
void FixApplication::fromApp(const Message& message, const SessionID& session_ID)
{
// Call MessageCracker.crack method to handle the message by one of our
// overloaded onMessage methods below
crack(message, session_ID);
}
// The TradingSessionStatus message is used to provide an update on the status of the market. Furthermore,
// this message contains useful system parameters as well as information about each trading security (embedded SecurityList).
// TradingSessionStatus should be requested upon successful Logon and subscribed to. The contents of the
// TradingSessionStatus message, specifically the SecurityList and system parameters, should dictate how fields
// are set when sending messages to FXCM.
void FixApplication::onMessage(const FIX44::TradingSessionStatus& tss, const SessionID& session_ID)
{
// Check TradSesStatus field to see if the trading desk is open or closed
// 2 = Open; 3 = Closed
string trad_status = tss.getField(FIELD::TradSesStatus);
cout << "TradingSessionStatus -> TradSesStatus -" << trad_status << endl;
// Within the TradingSessionStatus message is an embeded SecurityList. From SecurityList we can see
// the list of available trading securities and information relevant to each; e.g., point sizes,
// minimum and maximum order quantities by security, etc.
cout << " SecurityList via TradingSessionStatus -> " << endl;
int symbols_count = IntConvertor::convert(tss.getField(FIELD::NoRelatedSym));
for(int i = 1; i <= symbols_count; i++){
// Get the NoRelatedSym group and for each, print out the Symbol value
FIX44::SecurityList::NoRelatedSym symbols_group;
tss.getGroup(i,symbols_group);
string symbol = symbols_group.getField(FIELD::Symbol);
cout << " Symbol -> " << symbol << endl;
}
// Also within TradingSessionStatus are FXCM system parameters. This includes important information
// such as account base currency, server time zone, the time at which the trading day ends, and more.
cout << " System Parameters via TradingSessionStatus -> " << endl;
// Read field FXCMNoParam (9016) which shows us how many system parameters are
// in the message
int params_count = IntConvertor::convert(tss.getField(FXCM_NO_PARAMS)); // FXCMNoParam (9016)
for(int i = 1; i < params_count; i++){
// For each paramater, print out both the name of the paramater and the value of the
// paramater. FXCMParamName (9017) is the name of the paramater and FXCMParamValue(9018)
// is of course the paramater value
FIX::FieldMap field_map = tss.getGroupRef(i,FXCM_NO_PARAMS);
cout << " Param Name -> " << field_map.getField(FXCM_PARAM_NAME)
<< " - Param Value -> " << field_map.getField(FXCM_PARAM_VALUE) << endl;
}
// Request accounts under our login
GetAccounts();
// ** Note on Text(58) **
// You will notice that Text(58) field is always set to "Market is closed. Any trading
// functionality is not available." This field is always set to this value; therefore, do not
// use this field value to determine if the trading desk is open. As stated above, use TradSesStatus for this purpose
}
void FixApplication::onMessage(const FIX44::CollateralInquiryAck& ack, const SessionID& session_ID)
{
}
// CollateralReport is a message containing important information for each account under the login. It is returned
// as a response to CollateralInquiry. You will receive a CollateralReport for each account under your login.
// Notable fields include Account(1) which is the AccountID and CashOutstanding(901) which is the account balance
void FixApplication::onMessage(const FIX44::CollateralReport& cr, const SessionID& session_ID)
{
cout << "CollateralReport -> " << endl;
string accountID = cr.getField(FIELD::Account);
// Get account balance, which is the cash balance in the account, not including any profit
// or losses on open trades
string balance = cr.getField(FIELD::CashOutstanding);
cout << " AccountID -> " << accountID << endl;
cout << " Balance -> " << balance << endl;
// The CollateralReport NoPartyIDs group can be inspected for additional account information
// such as AccountName or HedgingStatus
FIX44::CollateralReport::NoPartyIDs group;
cr.getGroup(1,group); // CollateralReport will only have 1 NoPartyIDs group
cout << " Parties -> "<< endl;
// Get the number of NoPartySubIDs repeating groups
int number_subID = IntConvertor::convert(group.getField(FIELD::NoPartySubIDs));
// For each group, print out both the PartySubIDType and the PartySubID (the value)
for(int u = 1; u <= number_subID; u++){
FIX44::CollateralReport::NoPartyIDs::NoPartySubIDs sub_group;
group.getGroup(u,sub_group);
string sub_type = sub_group.getField(FIELD::PartySubIDType);
string sub_value = sub_group.getField(FIELD::PartySubID);
cout << " " << sub_type << " -> " << sub_value << endl;
}
// Add the accountID to our vector<string> being used to track all
// accounts under our login
RecordAccount(accountID);
}
void FixApplication::onMessage(const FIX44::RequestForPositionsAck& ack, const SessionID& session_ID)
{
string pos_reqID = ack.getField(FIELD::PosReqID);
cout << "RequestForPositionsAck -> PosReqID - " << pos_reqID << endl;
// If a PositionReport is requested and no positions exist for that request, the Text field will
// indicate that no positions mathced the requested criteria
if(ack.isSetField(FIELD::Text))
cout << "RequestForPositionsAck -> Text - " << ack.getField(FIELD::Text) << endl;
}
void FixApplication::onMessage(const FIX44::PositionReport& pr, const SessionID& session_ID)
{
// Print out important position related information such as accountID and symbol
string accountID = pr.getField(FIELD::Account);
string symbol = pr.getField(FIELD::Symbol);
string positionID = pr.getField(FXCM_POS_ID);
string pos_open_time = pr.getField(FXCM_POS_OPEN_TIME);
cout << "PositionReport -> " << endl;
cout << " Account -> " << accountID << endl;
cout << " Symbol -> " << symbol << endl;
cout << " PositionID -> " << positionID << endl;
cout << " Open Time -> " << pos_open_time << endl;
}
void FixApplication::onMessage(const FIX44::MarketDataRequestReject& mdr, const SessionID& session_ID)
{
// If MarketDataRequestReject is returned as the result of a MarketDataRequest message,
// print out the contents of the Text field but first check that it is set
cout << "MarketDataRequestReject -> " << endl;
if(mdr.isSetField(FIELD::Text)){
cout << " Text -> " << mdr.getField(FIELD::Text) << endl;
}
}
void FixApplication::onMessage(const FIX44::MarketDataSnapshotFullRefresh& mds, const SessionID& session_ID)
{
// Get symbol name of the snapshot; e.g., EUR/USD. Our example only subscribes to EUR/USD so
// this is the only possible value
string symbol = mds.getField(FIELD::Symbol);
// Declare variables for both the bid and ask prices. We will read the MarketDataSnapshotFullRefresh
// message for tthese values
double bid_price = 0;
double ask_price = 0;
// For each MDEntry in the message, inspect the NoMDEntries group for
// the presence of either the Bid or Ask (Offer) type
int entry_count = IntConvertor::convert(mds.getField(FIELD::NoMDEntries));
for(int i = 1; i < entry_count; i++){
FIX44::MarketDataSnapshotFullRefresh::NoMDEntries group;
mds.getGroup(i,group);
string entry_type = group.getField(FIELD::MDEntryType);
if(entry_type == "0"){ // Bid
bid_price = DoubleConvertor::convert(group.getField(FIELD::MDEntryPx));
}else if(entry_type == "1"){ // Ask (Offer)
ask_price = DoubleConvertor::convert(group.getField(FIELD::MDEntryPx));
}
}
cout << "MarketDataSnapshotFullRefresh -> Symbol - " << symbol
<< " Bid - " << bid_price << " Ask - " << ask_price << endl;
}
void FixApplication::onMessage(const FIX44::ExecutionReport& er, const SessionID& session_ID)
{
cout << "ExecutionReport -> " << endl;
cout << " ClOrdID -> " << er.getField(FIELD::ClOrdID) << endl;
cout << " Account -> " << er.getField(FIELD::Account) << endl;
cout << " OrderID -> " << er.getField(FIELD::OrderID) << endl;
cout << " LastQty -> " << er.getField(FIELD::LastQty) << endl;
cout << " CumQty -> " << er.getField(FIELD::CumQty) << endl;
cout << " ExecType -> " << er.getField(FIELD::ExecType) << endl;
cout << " OrdStatus -> " << er.getField(FIELD::OrdStatus) << endl;
// ** Note on order status. **
// In order to determine the status of an order, and also how much an order is filled, we must
// use the OrdStatus and CumQty fields. There are 3 possible final values for OrdStatus: Filled (2),
// Rejected (8), and Cancelled (4). When the OrdStatus field is set to one of these values, you know
// the execution is completed. At this time the CumQty (14) can be inspected to determine if and how
// much of an order was filled.
}
// Starts the FIX session. Throws FIX::ConfigError exception if our configuration settings
// do not pass validation required to construct SessionSettings
void FixApplication::StartSession()
{
try{
settings = new SessionSettings("settings.cfg");
store_factory = new FileStoreFactory(* settings);
log_factory = new FileLogFactory(* settings);
initiator = new SocketInitiator(* this, * store_factory, * settings, * log_factory/*Optional*/);
initiator->start();
}catch(ConfigError error){
cout << error.what() << endl;
}
}
// Logout and end session
void FixApplication::EndSession()
{
initiator->stop();
delete initiator;
delete settings;
delete store_factory;
delete log_factory;
}
// Sends TradingSessionStatusRequest message in order to receive as a response the
// TradingSessionStatus message
void FixApplication::GetTradingStatus()
{
// Request TradingSessionStatus message
FIX44::TradingSessionStatusRequest request;
request.setField(TradSesReqID(NextRequestID()));
request.setField(TradingSessionID("FXCM"));
request.setField(SubscriptionRequestType(SubscriptionRequestType_SNAPSHOT));
Session::sendToTarget(request, sessionID(false));
}
// Sends the CollateralInquiry message in order to receive as a response the
// CollateralReport message.
void FixApplication::GetAccounts()
{
// Request CollateralReport message. We will receive a CollateralReport for each
// account under our login
FIX44::CollateralInquiry request;
request.setField(CollInquiryID(NextRequestID()));
request.setField(TradingSessionID("FXCM"));
request.setField(SubscriptionRequestType(SubscriptionRequestType_SNAPSHOT));
Session::sendToTarget(request, sessionID(false));
}
// Sends RequestForPositions which will return PositionReport messages if positions
// matching the requested criteria exist; otherwise, a RequestForPositionsAck will be
// sent with the acknowledgement that no positions exist. In our example, we request
// positions for all accounts under our login
void FixApplication::GetPositions()
{
// Here we will get positions for each account under our login. To do this,
// we will send a RequestForPositions message that contains the accountID
// associated with our request. For each account in our list, we send
// RequestForPositions.
int total_accounts = (int)list_accountID.size();
for(int i = 0; i < total_accounts; i++){
string accountID = list_accountID.at(i);
// Set default fields
FIX44::RequestForPositions request;
request.setField(PosReqID(NextRequestID()));
request.setField(PosReqType(PosReqType_POSITIONS));
// AccountID for the request. This must be set for routing purposes. We must
// also set the Parties AccountID field in the NoPartySubIDs group
request.setField(Account(accountID));
request.setField(SubscriptionRequestType(SubscriptionRequestType_SNAPSHOT));
request.setField(AccountType(
AccountType_ACCOUNT_IS_CARRIED_ON_NON_CUSTOMER_SIDE_OF_BOOKS_AND_IS_CROSS_MARGINED));
request.setField(TransactTime());
request.setField(ClearingBusinessDate());
request.setField(TradingSessionID("FXCM"));
// Set NoPartyIDs group. These values are always as seen below
request.setField(NoPartyIDs(1));
FIX44::RequestForPositions::NoPartyIDs parties_group;
parties_group.setField(PartyID("FXCM ID"));
parties_group.setField(PartyIDSource('D'));
parties_group.setField(PartyRole(3));
parties_group.setField(NoPartySubIDs(1));
// Set NoPartySubIDs group
FIX44::RequestForPositions::NoPartyIDs::NoPartySubIDs sub_parties;
sub_parties.setField(PartySubIDType(PartySubIDType_SECURITIES_ACCOUNT_NUMBER));
// Set Parties AccountID
sub_parties.setField(PartySubID(accountID));
// Add NoPartySubIds group
parties_group.addGroup(sub_parties);
// Add NoPartyIDs group
request.addGroup(parties_group);
// Send request
Session::sendToTarget(request, sessionID(false));
}
}
// Subscribes to the EUR/USD trading security
void FixApplication::SubscribeMarketData(string strPair)
{
// Subscribe to market data for EUR/USD
string request_ID = strPair + "_Request_";
FIX44::MarketDataRequest request;
request.setField(MDReqID(request_ID));
request.setField(SubscriptionRequestType(
SubscriptionRequestType_SNAPSHOT_PLUS_UPDATES));
request.setField(MarketDepth(0));
request.setField(NoRelatedSym(1));
// Add the NoRelatedSym group to the request with Symbol
// field set to EUR/USD
FIX44::MarketDataRequest::NoRelatedSym symbols_group;
symbols_group.setField(Symbol(strPair));
request.addGroup(symbols_group);
// Add the NoMDEntryTypes group to the request for each MDEntryType
// that we are subscribing to. This includes Bid, Offer, High, and Low
FIX44::MarketDataRequest::NoMDEntryTypes entry_types;
entry_types.setField(MDEntryType(MDEntryType_BID));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_OFFER));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_TRADING_SESSION_HIGH_PRICE));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_TRADING_SESSION_LOW_PRICE));
request.addGroup(entry_types);
Session::sendToTarget(request, sessionID(true));
}
// Unsubscribes from the EUR/USD trading security
void FixApplication::UnsubscribeMarketData()
{
// Unsubscribe from EUR/USD. Note that our request_ID is the exact same
// that was sent for our request to subscribe. This is necessary to
// unsubscribe. This request below is identical to our request to subscribe
// with the exception that SubscriptionRequestType is set to
// "SubscriptionRequestType_DISABLE_PREVIOUS_SNAPSHOT_PLUS_UPDATE_REQUEST"
string request_ID = "EUR_USD_Request_";
FIX44::MarketDataRequest request;
request.setField(MDReqID(request_ID));
request.setField(SubscriptionRequestType(
SubscriptionRequestType_DISABLE_PREVIOUS_SNAPSHOT_PLUS_UPDATE_REQUEST));
request.setField(MarketDepth(0));
request.setField(NoRelatedSym(1));
// Add the NoRelatedSym group to the request with Symbol
// field set to EUR/USD
FIX44::MarketDataRequest::NoRelatedSym symbols_group;
symbols_group.setField(Symbol("EUR/USD"));
request.addGroup(symbols_group);
// Add the NoMDEntryTypes group to the request for each MDEntryType
// that we are subscribing to. This includes Bid, Offer, High, and Low
FIX44::MarketDataRequest::NoMDEntryTypes entry_types;
entry_types.setField(MDEntryType(MDEntryType_BID));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_OFFER));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_TRADING_SESSION_HIGH_PRICE));
request.addGroup(entry_types);
entry_types.setField(MDEntryType(MDEntryType_TRADING_SESSION_LOW_PRICE));
request.addGroup(entry_types);
Session::sendToTarget(request, sessionID(true));
}
// Sends a basic NewOrderSingle message to buy EUR/USD at the
// current market price
void FixApplication::MarketOrder()
{
// For each account in our list, send a NewOrderSingle message
// to buy EUR/USD. What differentiates this message is the
// accountID
int total_accounts = (int)list_accountID.size();
for(int i = 0; i < total_accounts; i++){
string accountID = list_accountID.at(i);
FIX44::NewOrderSingle request;
request.setField(ClOrdID(NextRequestID()));
request.setField(Account(accountID));
request.setField(Symbol("EUR/USD"));
request.setField(TradingSessionID("FXCM"));
request.setField(TransactTime());
request.setField(OrderQty(10000));
request.setField(Side(FIX::Side_BUY));
request.setField(OrdType(OrdType_MARKET));
request.setField(TimeInForce(FIX::TimeInForce_GOOD_TILL_CANCEL)); // For newer versions of QuickFIX change this to TimeInForce_GOOD_TILL_CANCEL
Session::sendToTarget(request, sessionID(false));
}
}
// Generate string value used to populate the fields in each message
// which are used as a custom identifier
string FixApplication::NextRequestID()
{
if(requestID == 65535)
requestID = 1;
requestID++;
string next_ID = IntConvertor::convert(requestID);
return next_ID;
}
// Adds string accountIDs to our vector<string> being used to
// account for the accountIDs under our login
void FixApplication::RecordAccount(string accountID)
{
int size = (int)list_accountID.size();
if(size == 0){
list_accountID.push_back(accountID);
}else{
for(int i = 0; i < size; i++){
if(list_accountID.at(i) == accountID)
break;
if(i == size - 1){
list_accountID.push_back(accountID);
}
}
}
}