1/*
2 * Legal Notice
3 *
4 * This document and associated source code (the "Work") is a part of a
5 * benchmark specification maintained by the TPC.
6 *
7 * The TPC reserves all right, title, and interest to the Work as provided
8 * under U.S. and international laws, including without limitation all patent
9 * and trademark rights therein.
10 *
11 * No Warranty
12 *
13 * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION
14 * CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE
15 * AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER
16 * WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
17 * INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES,
18 * DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR
19 * PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
20 * WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE.
21 * ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT,
22 * QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT
23 * WITH REGARD TO THE WORK.
24 * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO
25 * ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE
26 * COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS
27 * OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT,
28 * INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY,
29 * OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT
30 * RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD
31 * ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
32 *
33 * Contributors
34 * - Sergey Vasilevskiy
35 * - Doug Johnson
36 */
37
38#include "main/EGenTables_stdafx.h"
39#include "main/ExchangeIDs.h"
40
41using namespace TPCE;
42
43// Price change function period in seconds
44//
45const int iSecPricePeriod = 15 * SecondsPerMinute; // set to 15 minutes, in seconds
46
47// Number of RNG calls for one simulated trade
48const int iRNGSkipOneTrade = 11; // average count for v3.5: 6.5
49
50// Operator for priority queue
51//
52namespace TPCE {
53
54// Need const reference left argument for greater<TTradeInfo> comparison
55// function
56bool operator>(const TTradeInfo &l, const TTradeInfo &r) {
57 return l.CompletionTime > r.CompletionTime;
58}
59
60} // namespace TPCE
61
62/*
63 * Constructor.
64 * Creates priority queue.
65 *
66 */
67CTradeGen::CTradeGen(const DataFileManager &dfm, TIdent iCustomerCount, TIdent iStartFromCustomer,
68 TIdent iTotalCustomers, UINT iLoadUnitSize, UINT iScaleFactor, UINT iHoursOfInitialTrades,
69 bool bCacheEnabled)
70 : m_rnd(RNGSeedTradeGen), m_AddressTable(dfm, iCustomerCount, iStartFromCustomer, true,
71 bCacheEnabled) // only customer addresses
72 ,
73 m_CustomerSelection(&m_rnd, 0, 0, 100, iStartFromCustomer,
74 iLoadUnitSize) // only generate customer within partition
75 ,
76 m_CustomerTable(dfm, iCustomerCount, iStartFromCustomer),
77 m_CustTaxrateTable(dfm, iCustomerCount, iStartFromCustomer, bCacheEnabled),
78 m_CustomerAccountTable(dfm, iLoadUnitSize, iCustomerCount, iStartFromCustomer, bCacheEnabled),
79 m_HoldingTable(dfm, iLoadUnitSize, iCustomerCount, iStartFromCustomer, bCacheEnabled),
80 m_BrokerTable(dfm, iCustomerCount, iStartFromCustomer), m_SecurityTable(dfm, iCustomerCount, iStartFromCustomer),
81 m_Person(dfm, iStartFromCustomer, bCacheEnabled), m_CompanyFile(dfm.CompanyFile()),
82 m_SecurityFile(dfm.SecurityFile()), m_ChargeFile(dfm.ChargeDataFile()),
83 m_CommissionRateFile(dfm.CommissionRateDataFile()), m_StatusTypeFile(dfm.StatusTypeDataFile()),
84 m_TradeTypeFile(dfm.TradeTypeDataFile()), m_ExchangeFile(dfm.ExchangeDataFile()),
85 m_iStartFromCustomer(iStartFromCustomer + iTIdentShift), m_iCustomerCount(iCustomerCount),
86 m_iTotalCustomers(iTotalCustomers), m_iLoadUnitSize(iLoadUnitSize),
87 m_iLoadUnitAccountCount(iLoadUnitSize * iMaxAccountsPerCust), m_iScaleFactor(iScaleFactor),
88 m_iHoursOfInitialTrades(iHoursOfInitialTrades),
89 m_fMeanTimeBetweenTrades(100.0 / iAbortTrade * (double)iScaleFactor / iLoadUnitSize),
90 m_fMeanInTheMoneySubmissionDelay(1.0), m_CurrentSimulatedTime(0), m_iCurrentCompletedTrades(0),
91 m_iTotalTrades((TTrade)iHoursOfInitialTrades * SecondsPerHour * iLoadUnitSize / iScaleFactor),
92 m_iCurrentInitiatedTrades(0),
93 m_iTradesPerWorkDay(HoursPerWorkDay * SecondsPerHour * iLoadUnitSize / iScaleFactor * iAbortTrade / 100),
94 m_MEESecurity(), m_iCurrentAccountForHolding(0), m_iCurrentSecurityForHolding(0), m_pCurrentSecurityHolding(),
95 m_iCurrentAccountForHoldingSummary(0),
96 m_iCurrentSecurityForHoldingSummary(-1) // incremented in FindNextHoldingList()
97 ,
98 m_iCurrentLoadUnit(0) {
99 RNGSEED RNGSkipCount;
100
101 // Set the start time (time 0) to the base time
102 m_StartTime.Set(InitialTradePopulationBaseYear, InitialTradePopulationBaseMonth, InitialTradePopulationBaseDay,
103 InitialTradePopulationBaseHour, InitialTradePopulationBaseMinute, InitialTradePopulationBaseSecond,
104 InitialTradePopulationBaseFraction);
105
106 // Get the first account number
107 //
108 m_iStartFromAccount = m_CustomerAccountTable.GetStartingCA_ID(m_iStartFromCustomer);
109
110 // Create an array of customer holding lists
111 //
112 m_pCustomerHoldings = new THoldingList[m_iLoadUnitAccountCount][iMaxSecuritiesPerAccount];
113
114 // Clear row structures
115 //
116 memset(&m_NewTrade, 0, sizeof(m_NewTrade));
117 memset(&m_TradeRow, 0, sizeof(m_TradeRow));
118 memset(&m_HoldingRow, 0, sizeof(m_HoldingRow));
119 memset(&m_HoldingSummaryRow, 0, sizeof(m_HoldingSummaryRow));
120
121 // Position trade id at the proper start of the sequence
122 //
123 m_iCurrentTradeId = (TTrade)m_iHoursOfInitialTrades * SecondsPerHour *
124 (iStartFromCustomer - iDefaultStartFromCustomer) /
125 m_iScaleFactor // divide after multiplication to
126 // avoid integer truncation
127 * iAbortTrade / 100 +
128 iTTradeShift;
129
130 // Initialize BROKER table
131 //
132 m_BrokerTable.InitForGen(iLoadUnitSize, m_iStartFromCustomer - iTIdentShift);
133
134 RNGSkipCount = (RNGSEED)(m_iStartFromCustomer / m_iLoadUnitSize * m_iTotalTrades);
135
136 m_rnd.SetSeed(m_rnd.RndNthElement(RNGSeedTradeGen, RNGSkipCount * iRNGSkipOneTrade));
137
138 m_HoldingTable.InitNextLoadUnit(RNGSkipCount, m_iStartFromAccount);
139
140 // Initialize security price emulation
141 m_MEESecurity.Init(0, NULL, NULL, m_fMeanInTheMoneySubmissionDelay);
142}
143
144/*
145 * Destructor.
146 * Frees any memory allocated in the constructor.
147 */
148CTradeGen::~CTradeGen() {
149 if (m_pCustomerHoldings != NULL) {
150 delete[] m_pCustomerHoldings;
151 }
152}
153
154/*
155 * Initialize next load unit for a series of
156 * GenerateNextTrade/GenerateNextHolding calls.
157 *
158 * The first load unit doesn't have to be initalized.
159 *
160 * RETURNS:
161 * true - if a new load unit could be found
162 * false - if all load units have been processed
163 */
164bool CTradeGen::InitNextLoadUnit() {
165 RNGSEED RNGSkipCount;
166
167 ++m_iCurrentLoadUnit;
168
169 m_iCurrentCompletedTrades = 0;
170
171 // No need to empty holdings as they were emptied by
172 // GenerateNextHolding calls.
173 //
174 delete[] m_pCustomerHoldings;
175 // Create an array of customer holding lists
176 //
177 m_pCustomerHoldings = new THoldingList[m_iLoadUnitAccountCount][iMaxSecuritiesPerAccount];
178 m_iCurrentAccountForHolding = 0;
179 m_iCurrentSecurityForHolding = 0;
180
181 m_iCurrentAccountForHoldingSummary = 0;
182 m_iCurrentSecurityForHoldingSummary = -1;
183
184 m_iStartFromCustomer += m_iLoadUnitSize;
185
186 m_iStartFromAccount = m_CustomerAccountTable.GetStartingCA_ID(m_iStartFromCustomer);
187
188 m_CurrentSimulatedTime = 0;
189
190 m_iCurrentInitiatedTrades = 0;
191
192 m_BrokerTable.InitForGen(m_iLoadUnitSize, m_iStartFromCustomer - iTIdentShift);
193
194 RNGSkipCount = (RNGSEED)(m_iStartFromCustomer / m_iLoadUnitSize * m_iTotalTrades);
195
196 m_rnd.SetSeed(m_rnd.RndNthElement(RNGSeedTradeGen, RNGSkipCount * iRNGSkipOneTrade));
197
198 // Move customer range for the next load unit.
199 //
200 m_CustomerSelection.SetPartitionRange(m_iStartFromCustomer, m_iLoadUnitSize);
201
202 m_HoldingTable.InitNextLoadUnit(RNGSkipCount, m_iStartFromAccount);
203
204 m_Person.InitNextLoadUnit();
205
206 m_AddressTable.InitNextLoadUnit();
207
208 m_CustomerAccountTable.InitNextLoadUnit();
209
210 m_MEESecurity.Init(0, NULL, NULL, m_fMeanInTheMoneySubmissionDelay);
211
212 // Clear row structures.
213 //
214 memset(&m_TradeRow, 0, sizeof(m_TradeRow));
215 memset(&m_HoldingRow, 0, sizeof(m_HoldingRow));
216 memset(&m_HoldingSummaryRow, 0, sizeof(m_HoldingSummaryRow));
217
218 return m_iCurrentLoadUnit < (m_iCustomerCount / m_iLoadUnitSize);
219}
220
221/*
222 * Generate a new trade and all trade-related rows (except holdings).
223 *
224 * PARAMETERS:
225 * none
226 *
227 * RETURNS:
228 * true - if there are more trades
229 * false - if there are no more trades to generate
230 *
231 */
232bool CTradeGen::GenerateNextTrade() {
233 bool bMoreTrades;
234
235 if (m_iCurrentCompletedTrades < m_iTotalTrades) {
236 // While the earliest completion time is before the current
237 // simulated ('Trade Order') time, keep creating new
238 // incomplete trades putting them on the queue and
239 // incrementing the current simulated time.
240 //
241 while ((m_iCurrentCompletedTrades + (int)m_CurrentTrades.size() < m_iTotalTrades) &&
242 (m_CurrentTrades.empty() || (m_CurrentSimulatedTime < m_CurrentTrades.top().CompletionTime))) {
243 m_CurrentSimulatedTime = (m_iCurrentInitiatedTrades / m_iTradesPerWorkDay) // number of days
244 * SecondsPerDay // number of seconds in a day
245 // now add the offset in the day
246 + (m_iCurrentInitiatedTrades % m_iTradesPerWorkDay) * m_fMeanTimeBetweenTrades +
247 GenerateDelayBetweenTrades();
248
249 // Generate new Trade Order; CMEESecurity
250 // is used by this function.
251 //
252 GenerateNewTrade();
253
254 if (m_HoldingTable.IsAbortedTrade(m_iCurrentInitiatedTrades)) {
255 // Abort trade and not put it into the queue.
256 // Generate a new trade instead.
257 //
258 continue;
259 }
260
261 m_CurrentTrades.push(m_NewTrade);
262 }
263
264 // Get the earliest trade from the
265 // front of the queue.
266 //
267 m_NewTrade = m_CurrentTrades.top();
268
269 // Remove the top element.
270 //
271 m_CurrentTrades.pop();
272
273 // Update HOLDING row for the customer
274 //
275 // Must be called before generating the complete trade
276 // to calculate buy and sell values for T_TAX.
277 //
278 UpdateHoldings();
279
280 // Generate all the remaining trade data.
281 //
282 GenerateCompleteTrade();
283
284 bMoreTrades = m_iCurrentCompletedTrades < m_iTotalTrades;
285
286 } else {
287 bMoreTrades = false;
288 }
289
290 if (bMoreTrades) {
291 return true;
292 } else {
293 // Before returning need to position
294 // holding iterator for GenerateNextHolding()
295 //
296 m_pCurrentSecurityHolding =
297 m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding].begin();
298 FindNextHolding();
299
300 // Set up for GenerateNextHoldingSummary
301 FindNextHoldingList();
302
303 size_t iSize = m_CurrentTrades.size(); // info for debugging
304
305 assert(iSize == 0);
306
307 return false;
308 }
309}
310
311/*
312 * Generate a random delay in Submission (or Pending for limit trades)
313 * time between two trades.
314 *
315 * PARAMETERS:
316 * none
317 *
318 * RETURNS:
319 * seconds of delay before simulated time for the next trade
320 *
321 */
322inline double CTradeGen::GenerateDelayBetweenTrades() {
323 // Return a random number between 0 and 1ms less than the mean.
324 //
325 return m_rnd.RndDoubleIncrRange(0.0, m_fMeanTimeBetweenTrades - 0.001, 0.001);
326}
327
328/*
329 * Helper function to get the list of holdings
330 * to modify after the last completed trade
331 *
332 * RETURNS:
333 * reference to the list of holdings
334 */
335inline THoldingList *CTradeGen::GetHoldingListForCurrentTrade() {
336 return &m_pCustomerHoldings[GetCurrentAccID() - m_iStartFromAccount][GetCurrentSecurityAccountIndex() - 1];
337}
338
339/*
340 * Helper function to get either the front or the end
341 * of the holding list
342 *
343 * RETURNS:
344 * iterator positined at the first element or at the last element
345 */
346list<THoldingInfo>::iterator CTradeGen::PositionAtHoldingList(THoldingList *pHoldingList, int IsLifo) {
347 if (pHoldingList->empty()) {
348 return pHoldingList->end(); // iterator positioned after the last element
349 } else {
350
351 if (IsLifo) {
352 return --(pHoldingList->end()); // position before the last element
353 } else {
354 return pHoldingList->begin();
355 }
356 }
357}
358
359/*
360 * Update holding information based on the trade row
361 * internal structures.
362 * In other words, update holdings for the last trade
363 * that was generated (by GenerateCompleteTrade()).
364 *
365 * PARAMETERS:
366 * none
367 *
368 * RETURNS:
369 * none
370 *
371 */
372void CTradeGen::UpdateHoldings() {
373 THoldingList *pHoldingList;
374 list<THoldingInfo>::iterator pHolding; // start of the holding list
375 int iNeededQty = GetCurrentTradeQty();
376 int iHoldQty;
377
378 m_CompletedTradeInfo.fBuyValue = 0;
379 m_CompletedTradeInfo.fSellValue = 0;
380
381 // Start new series of HOLDING_HISTORY rows
382 //
383 m_iHoldingHistoryRowCount = 0;
384
385 pHoldingList = GetHoldingListForCurrentTrade();
386
387 pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo());
388
389 if (GetCurrentTradeType() == eMarketBuy || GetCurrentTradeType() == eLimitBuy) {
390 // Buy trade
391
392 // Liquidate negative (short) holdings
393 //
394 while (!pHoldingList->empty() && pHolding->iTradeQty < 0 && iNeededQty > 0) {
395 iHoldQty = pHolding->iTradeQty;
396
397 pHolding->iTradeQty += iNeededQty;
398
399 if (pHolding->iTradeQty > 0) {
400 // Need to zero the qty for correct history row later
401 //
402 pHolding->iTradeQty = 0; // holding fully closed
403
404 m_CompletedTradeInfo.fSellValue += -iHoldQty * pHolding->fTradePrice;
405 m_CompletedTradeInfo.fBuyValue += -iHoldQty * GetCurrentTradePrice();
406 } else {
407 m_CompletedTradeInfo.fSellValue += iNeededQty * pHolding->fTradePrice;
408 m_CompletedTradeInfo.fBuyValue += iNeededQty * GetCurrentTradePrice();
409 }
410
411 GenerateHoldingHistoryRow(pHolding->iTradeId, GetCurrentTradeID(), iHoldQty, pHolding->iTradeQty);
412
413 if (pHolding->iTradeQty == 0) {
414 // There was enough new quantity to fully close the old holding
415 //
416 pHolding = pHoldingList->erase(pHolding); // erase the old and return next holding
417 }
418
419 // Reposition at proper end
420 //
421 pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo());
422
423 iNeededQty += iHoldQty;
424 }
425
426 if (iNeededQty > 0) {
427 // Still shares left after closing positions => create a new holding
428 //
429 THoldingInfo NewHolding = {GetCurrentTradeID(), iNeededQty, GetCurrentTradePrice(),
430 GetCurrentTradeCompletionTime(), GetCurrentSecurityIndex()};
431
432 // Note: insert should be at the same end all the time
433 // provided delete (PositionAtHoldingList()) is different
434 // depending on IsLifo.
435 // Vice versa also works - delete is at the
436 // same end and insert depends on IsLifo. However, TradeResult
437 // inserts at the end, so let loader insert in the same end.
438 //
439 pHoldingList->push_back(NewHolding);
440
441 GenerateHoldingHistoryRow(GetCurrentTradeID(), GetCurrentTradeID(), 0, iNeededQty);
442 }
443 } else {
444 // Sell trade
445
446 // iNeededQty *= (-1); // make trade qty negative for convenience
447
448 // Liquidate positive (long) holdings
449 //
450 while (!pHoldingList->empty() && pHolding->iTradeQty > 0 && iNeededQty > 0) {
451 iHoldQty = pHolding->iTradeQty;
452
453 pHolding->iTradeQty -= iNeededQty;
454
455 if (pHolding->iTradeQty < 0) {
456 // Need to zero the qty for correct history row later
457 //
458 pHolding->iTradeQty = 0; // holding fully closed
459
460 m_CompletedTradeInfo.fSellValue += iHoldQty * GetCurrentTradePrice();
461 m_CompletedTradeInfo.fBuyValue += iHoldQty * pHolding->fTradePrice;
462 } else {
463 m_CompletedTradeInfo.fSellValue += iNeededQty * GetCurrentTradePrice();
464 m_CompletedTradeInfo.fBuyValue += iNeededQty * pHolding->fTradePrice;
465 }
466
467 GenerateHoldingHistoryRow(pHolding->iTradeId, GetCurrentTradeID(), iHoldQty, pHolding->iTradeQty);
468
469 if (pHolding->iTradeQty == 0) {
470 // There was enough new quantity to fully close the old holding
471 //
472 pHolding = pHoldingList->erase(pHolding); // erase the old and return next holding
473 }
474
475 // Reposition at proper end
476 //
477 pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo());
478
479 iNeededQty -= iHoldQty;
480 }
481
482 if (iNeededQty > 0) {
483 // Still shares left after closing positions => create a new holding
484 //
485 THoldingInfo NewHolding = {GetCurrentTradeID(), -iNeededQty, GetCurrentTradePrice(),
486 GetCurrentTradeCompletionTime(), GetCurrentSecurityIndex()};
487
488 // Note: insert should be at the same end all the time
489 // provided delete (PositionAtHoldingList()) is different
490 // depending on IsLifo.
491 // Vice versa also works - delete is at the
492 // same end and insert depends on IsLifo. However, TradeResult
493 // inserts at the end, so let loader insert in the same end.
494 //
495 pHoldingList->push_back(NewHolding);
496
497 GenerateHoldingHistoryRow(GetCurrentTradeID(), GetCurrentTradeID(), 0, -iNeededQty);
498 }
499 }
500}
501
502/*
503 * Find next non-empty holding list.
504 *
505 * RETURNS:
506 * whether a non-empty holding list exists
507 */
508bool CTradeGen::FindNextHoldingList() {
509 THoldingList *pHoldingList;
510
511 // Find the next non-empty holding list
512 //
513 do {
514 m_iCurrentSecurityForHoldingSummary++;
515 if (m_iCurrentSecurityForHoldingSummary == iMaxSecuritiesPerAccount) {
516 // no more securities for the current account
517 // so move on to next account.
518 //
519 m_iCurrentAccountForHoldingSummary++;
520 m_iCurrentSecurityForHoldingSummary = 0;
521
522 if (m_iCurrentAccountForHoldingSummary == m_iLoadUnitAccountCount) {
523 // no more customers left, all holding lists have been processed
524 //
525 return false;
526 }
527 }
528
529 // Set list pointer
530 //
531 pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHoldingSummary][m_iCurrentSecurityForHoldingSummary];
532 } while (pHoldingList->empty()); // test for empty HoldingList
533
534 return true;
535}
536
537/*
538 * Find non-empty holding and position internal
539 * iterator at it.
540 *
541 * RETURNS:
542 * whether a non-empty holding exists
543 */
544bool CTradeGen::FindNextHolding() {
545 THoldingList *pHoldingList;
546
547 pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding];
548
549 // Make sure the holding iterator points to a valid holding
550 //
551 do {
552 if (m_pCurrentSecurityHolding == pHoldingList->end()) {
553 // no more holding for the security => have to move to the next
554 // security
555 //
556 ++m_iCurrentSecurityForHolding;
557
558 if (m_iCurrentSecurityForHolding == iMaxSecuritiesPerAccount) {
559
560 // no more holding for the account => have to move to the next
561 // account
562 //
563 ++m_iCurrentAccountForHolding;
564
565 m_iCurrentSecurityForHolding = 0;
566
567 if (m_iCurrentAccountForHolding == m_iLoadUnitAccountCount) {
568 // no more customers left => all holdings have been returned
569 //
570 return false;
571 }
572 }
573
574 // Holding list has changed => reinitialize
575 //
576 pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding];
577 // Select the first holding in the new list
578 //
579 m_pCurrentSecurityHolding = pHoldingList->begin();
580 }
581 } while (m_pCurrentSecurityHolding == pHoldingList->end()); // test for empty HoldingList
582
583 return true;
584}
585
586/*
587 * Generate a new HOLDING_SUMMARY row.
588 * This function uses the lists of holdings generated
589 * during simulated trade generation and returns the
590 * row for the current account/security pair.
591 *
592 * PARAMETERS:
593 * none
594 * RETURNS:
595 * true - if there are more holding lists
596 * false - if there are no more holding lists
597 */
598bool CTradeGen::GenerateNextHoldingSummaryRow() {
599 TIdent iSecurityFlatFileIndex; // index of the security in the input flat file
600
601 if (m_iCurrentAccountForHoldingSummary < m_iLoadUnitAccountCount) {
602 // There is always a valid holding list when this function
603 // is called. The holding list to process is identified by
604 // m_iCurrentAccountForHoldingSummary and
605 // m_iCurrentSecurityForHoldingSummary.
606 //
607 m_HoldingSummaryRow.HS_CA_ID = m_iCurrentAccountForHoldingSummary + m_iStartFromAccount;
608 iSecurityFlatFileIndex = m_HoldingTable.GetSecurityFlatFileIndex(
609 m_iCurrentAccountForHoldingSummary + m_iStartFromAccount, (UINT)(m_iCurrentSecurityForHoldingSummary + 1));
610
611 m_SecurityFile.CreateSymbol(iSecurityFlatFileIndex, m_HoldingSummaryRow.HS_S_SYMB,
612 static_cast<int>(sizeof(m_HoldingSummaryRow.HS_S_SYMB)));
613
614 // Sum up the quantities for the holding list
615 THoldingList *pHoldingList;
616 list<THoldingInfo>::iterator pHolding;
617
618 pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHoldingSummary][m_iCurrentSecurityForHoldingSummary];
619 pHolding = pHoldingList->begin();
620 m_HoldingSummaryRow.HS_QTY = 0;
621
622 while (pHolding != pHoldingList->end()) {
623 m_HoldingSummaryRow.HS_QTY += pHolding->iTradeQty;
624 pHolding++;
625 }
626
627 return FindNextHoldingList();
628 } else {
629 return false;
630 }
631}
632
633/*
634 * Generate a new HOLDING_HISTORY row and update HOLDING_HISTORY row count.
635 *
636 * RETURNS:
637 * none
638 */
639void CTradeGen::GenerateHoldingHistoryRow(TTrade iHoldingTradeID, // trade id of the original trade
640 TTrade iTradeTradeID, // trade id of the modifying trade
641 int iBeforeQty, // holding qty now
642 int iAfterQty) // holding qty after modification
643{
644 if (m_iHoldingHistoryRowCount < iMaxHoldingHistoryRowsPerTrade) {
645 m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_H_T_ID = iHoldingTradeID;
646 m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_T_ID = iTradeTradeID;
647 m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_BEFORE_QTY = iBeforeQty;
648 m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_AFTER_QTY = iAfterQty;
649
650 ++m_iHoldingHistoryRowCount;
651 }
652}
653
654/*
655 * Generate a new holding row.
656 * This function uses already prepared holding list structure
657 * and returns the next holding for the current customer.
658 *
659 * The returned holding is deleted from the holding list
660 * to clear the list for the next load unit.
661 *
662 * It moves to the next customer if the current one doesn't
663 * have any more holdings.
664 *
665 * PARAMETERS:
666 * none
667 * RETURNS:
668 * true - if there are more holdings
669 * false - if there are no more holdings to return
670 */
671bool CTradeGen::GenerateNextHolding() {
672 TIdent iSecurityFlatFileIndex; // index of the security in the input flat file
673
674 if (m_iCurrentAccountForHolding < m_iLoadUnitAccountCount) {
675 // There is always a valid holding when this function
676 // is called. The holding to put into the HOLDING row
677 // is pointed to by m_pCurrentSecurityHolding.
678 //
679 m_HoldingRow.H_CA_ID = m_iCurrentAccountForHolding + m_iStartFromAccount;
680 // iSecurityFlatFileIndex = m_HoldingTable.GetSecurityFlatFileIndex(
681 // m_iCurrentAccountForHolding
682 // + m_iStartFromAccount,
683 // m_iCurrentSecurityForHolding
684 // + 1);
685
686 iSecurityFlatFileIndex = m_pCurrentSecurityHolding->iSymbolIndex;
687
688 m_SecurityFile.CreateSymbol(iSecurityFlatFileIndex, m_HoldingRow.H_S_SYMB,
689 static_cast<int>(sizeof(m_HoldingRow.H_S_SYMB)));
690
691 m_HoldingRow.H_T_ID = m_pCurrentSecurityHolding->iTradeId;
692 m_HoldingRow.H_QTY = m_pCurrentSecurityHolding->iTradeQty;
693 m_HoldingRow.H_PRICE = m_pCurrentSecurityHolding->fTradePrice.DollarAmount();
694 m_HoldingRow.H_DTS = m_pCurrentSecurityHolding->BuyDTS;
695
696 // Delete the holding and move to the next one in the account
697 /*m_pCurrentSecurityHolding =
698 m_pCustomerHoldings[m_iCurrentAccountForHolding]
699 [m_iCurrentSecurityForHolding].erase(m_pCurrentSecurityHolding);*/
700
701 ++m_pCurrentSecurityHolding;
702
703 // Move to the next valid holding for the next call.
704 //
705 return FindNextHolding();
706 } else {
707 return false;
708 }
709}
710
711/*
712 * Generate a trade id for the next trade.
713 *
714 * PARAMETERS:
715 * none
716 * RETURNS:
717 * a new unique trade id
718 */
719TTrade CTradeGen::GenerateNextTradeId() {
720 return ++m_iCurrentTradeId;
721}
722
723/*
724 * Generates a random trade type according to a certain distribution
725 *
726 * PARAMETERS:
727 * none
728 * RETURNS:
729 * trade type id
730 */
731eTradeTypeID CTradeGen::GenerateTradeType() {
732 eTradeTypeID eTradeType;
733 // Generate Trade Type
734 // NOTE: The order of these "if" tests is significant!!
735 // Do not alter it unless you know what you are doing.
736 // :-)
737 //
738 int iLoadTradeTypePct = m_rnd.RndGenerateIntegerPercentage();
739
740 if (iLoadTradeTypePct <= cMarketBuyLoadThreshold) // 1% - 30%
741 {
742 eTradeType = eMarketBuy;
743 } else if (iLoadTradeTypePct <= cMarketSellLoadThreshold) // 31% - 60%
744 {
745 eTradeType = eMarketSell;
746 } else if (iLoadTradeTypePct <= cLimitBuyLoadThreshold) // 61% - 80%
747 {
748 eTradeType = eLimitBuy;
749 } else if (iLoadTradeTypePct <= cLimitSellLoadThreshold) // 81% - 90%
750 {
751 eTradeType = eLimitSell;
752 } else if (iLoadTradeTypePct <= cStopLossLoadThreshold) // 91% - 100%
753 {
754 eTradeType = eStopLoss;
755 } else {
756 assert(false); // this should never happen
757 }
758
759 return eTradeType;
760}
761
762/*
763 * Generate new incomplete trade (happens at Trade Order time)
764 * with enough information to later generate a complete one.
765 *
766 * PARAMETERS:
767 * none
768 *
769 * RETURNS:
770 * none
771 */
772void CTradeGen::GenerateNewTrade() {
773 m_NewTrade.iTradeId = GenerateNextTradeId();
774
775 // Select random customer
776 //
777 m_CustomerSelection.GenerateRandomCustomer(m_NewTrade.iCustomer, m_NewTrade.iCustomerTier);
778
779 // Select random customer, account, and security within the account
780 //
781 m_HoldingTable.GenerateRandomAccountSecurity(m_NewTrade.iCustomer, m_NewTrade.iCustomerTier,
782 &m_NewTrade.iCustomerAccount, &m_NewTrade.iSymbolIndex,
783 &m_NewTrade.iSymbolIndexInAccount);
784
785 m_NewTrade.eTradeType = GenerateTradeType();
786
787 // Status is always 'Completed' for initial trades
788 //
789 m_NewTrade.eTradeStatus = eCompleted;
790
791 m_NewTrade.fBidPrice = m_rnd.RndDoubleIncrRange(fMinSecPrice, fMaxSecPrice, 0.01);
792
793 m_NewTrade.iTradeQty = cTRADE_QTY_SIZES[m_rnd.RndIntRange(0, cNUM_TRADE_QTY_SIZES - 1)];
794
795 if (m_NewTrade.eTradeType == eMarketBuy || m_NewTrade.eTradeType == eMarketSell) { // A Market order
796 //
797 m_NewTrade.SubmissionTime = m_CurrentSimulatedTime;
798
799 // Update the bid price to the current market price (like runtime)
800 //
801 m_NewTrade.fBidPrice = m_MEESecurity.CalculatePrice(m_NewTrade.iSymbolIndex, m_CurrentSimulatedTime);
802 } else { // a Limit Order => need to calculate the Submission time
803 //
804 m_NewTrade.PendingTime = m_CurrentSimulatedTime;
805
806 m_NewTrade.SubmissionTime = m_MEESecurity.GetSubmissionTime(m_NewTrade.iSymbolIndex, m_NewTrade.PendingTime,
807 m_NewTrade.fBidPrice, m_NewTrade.eTradeType);
808
809 // Move orders that would submit after market close (5pm)
810 // to the beginning of the next day.
811 //
812 // Submission time here is kept from the beginning of the day, even
813 // though it is later output to the database starting from 9am. So time
814 // 0h corresponds to 9am, time 8hours corresponds to 5pm.
815 //
816 if ((((INT32)(m_NewTrade.SubmissionTime / SecondsPerHour)) % HoursPerDay == HoursPerWorkDay) && // >=5pm
817 ((m_NewTrade.SubmissionTime / SecondsPerHour) - ((INT32)(m_NewTrade.SubmissionTime / SecondsPerHour)) >
818 0 // fractional seconds exist, e.g. not 5:00pm
819 )) {
820 m_NewTrade.SubmissionTime += 16 * SecondsPerHour; // add 16 hours to move to 9am next day
821 }
822 }
823
824 // Calculate Completion time and price
825 //
826 m_NewTrade.CompletionTime =
827 m_MEESecurity.GetCompletionTime(m_NewTrade.iSymbolIndex, m_NewTrade.SubmissionTime, &m_NewTrade.fTradePrice);
828
829 // Make sure the trade has the right price based on the type of trade.
830 if ((m_NewTrade.eTradeType == eLimitBuy && m_NewTrade.fBidPrice < m_NewTrade.fTradePrice) ||
831 (m_NewTrade.eTradeType == eLimitSell && m_NewTrade.fBidPrice > m_NewTrade.fTradePrice)) {
832 m_NewTrade.fTradePrice = m_NewTrade.fBidPrice;
833 }
834
835 if (m_rnd.RndPercent(iPercentTradeIsLIFO)) {
836 m_NewTrade.bIsLifo = true;
837 } else {
838 m_NewTrade.bIsLifo = false;
839 }
840
841 ++m_iCurrentInitiatedTrades;
842}
843
844/*
845 * Generate a complete trade information
846 * and fill all the internal row structures.
847 *
848 * A valid incomplete trade must exist in m_NewTrade.
849 *
850 * PARAMETERS:
851 * none
852 *
853 * RETURNS:
854 * none.
855 *
856 */
857void CTradeGen::GenerateCompleteTrade() {
858 GenerateCompletedTradeInfo();
859
860 GenerateTradeRow(); // TRADE row must be generated before all the others
861 GenerateTradeHistoryRow();
862 GenerateCashTransactionRow();
863 GenerateSettlementRow();
864
865 m_BrokerTable.UpdateTradeAndCommissionYTD(GetCurrentBrokerId(), 1, m_TradeRow.m_Trade.T_COMM);
866
867 ++m_iCurrentCompletedTrades;
868}
869
870/*
871 * Generate frequently used fields for the completed trade.
872 * This function must be called before generating individual
873 * table rows.
874 *
875 * A valid incomplete trade must exist in m_NewTrade.
876 *
877 * PARAMETERS:
878 * none
879 *
880 * RETURNS:
881 * none.
882 *
883 */
884void CTradeGen::GenerateCompletedTradeInfo() {
885 m_CompletedTradeInfo.eAccountTaxStatus = m_CustomerAccountTable.GetAccountTaxStatus(GetCurrentAccID());
886
887 m_CompletedTradeInfo.iCurrentBrokerId = // not needed anymore?
888 m_CustomerAccountTable.GenerateBrokerIdForAccount(GetCurrentAccID());
889
890 GenerateTradeCharge(); // generate charge
891
892 GenerateTradeCommission(); // generate commission
893
894 GenerateTradeTax();
895
896 GenerateSettlementAmount();
897}
898
899/*
900 * Generate complete TRADE row information.
901 *
902 * PARAMETERS:
903 * none
904 *
905 * RETURNS:
906 * none
907 *
908 */
909void CTradeGen::GenerateTradeRow() {
910 m_TradeRow.m_Trade.T_ID = GetCurrentTradeID();
911
912 m_TradeRow.m_Trade.T_CA_ID = GetCurrentAccID();
913
914 strncpy(m_TradeRow.m_Trade.T_TT_ID, m_TradeTypeFile[GetCurrentTradeType()].TT_ID_CSTR(),
915 sizeof(m_TradeRow.m_Trade.T_TT_ID));
916
917 strncpy(m_TradeRow.m_Trade.T_ST_ID, m_StatusTypeFile[GetCurrentTradeStatus()].ST_ID_CSTR(),
918 sizeof(m_TradeRow.m_Trade.T_ST_ID));
919
920 // Generate whether the trade is cash trade. All sells are cash. 84% of buys
921 // are cash.
922 //
923 m_TradeRow.m_Trade.T_IS_CASH = 1; // changed later if needed
924
925 if (((GetCurrentTradeType() == eMarketBuy) || (GetCurrentTradeType() == eLimitBuy)) &&
926 m_rnd.RndPercent(iPercentBuysOnMargin)) {
927 m_TradeRow.m_Trade.T_IS_CASH = 0;
928 }
929
930 snprintf(m_TradeRow.m_Trade.T_EXEC_NAME, sizeof(m_TradeRow.m_Trade.T_EXEC_NAME), "%s %s",
931 m_Person.GetFirstName(GetCurrentCustID()).c_str(), m_Person.GetLastName(GetCurrentCustID()).c_str());
932
933 m_SecurityFile.CreateSymbol(GetCurrentSecurityIndex(), m_TradeRow.m_Trade.T_S_SYMB,
934 static_cast<int>(sizeof(m_TradeRow.m_Trade.T_S_SYMB)));
935
936 m_TradeRow.m_Trade.T_BID_PRICE = GetCurrentBidPrice().DollarAmount();
937
938 m_TradeRow.m_Trade.T_TRADE_PRICE = GetCurrentTradePrice().DollarAmount();
939
940 m_TradeRow.m_Trade.T_QTY = GetCurrentTradeQty();
941
942 m_TradeRow.m_Trade.T_CHRG = m_CompletedTradeInfo.Charge.DollarAmount(); // get charge
943
944 m_TradeRow.m_Trade.T_COMM = m_CompletedTradeInfo.Commission.DollarAmount(); // get commission
945
946 // Get the tax amount. The check for positive capital gain is
947 // in GenerateTradeTax(). If there is no capital gain, tax amount
948 // will be set to zero by this time.
949 //
950 switch (GetCurrentTaxStatus()) {
951 case eNonTaxable: // no taxes
952 m_TradeRow.m_Trade.T_TAX = 0;
953 break;
954 case eTaxableAndWithhold: // calculate and withhold
955 m_TradeRow.m_Trade.T_TAX = GetCurrentTradeTax().DollarAmount();
956 break;
957 case eTaxableAndDontWithhold: // calculate and do not withhold
958 m_TradeRow.m_Trade.T_TAX = GetCurrentTradeTax().DollarAmount();
959 break;
960 default: // should never happen
961 assert(false);
962 }
963
964 // T_DTS contains trade completion time.
965 //
966 m_TradeRow.m_Trade.T_DTS = GetCurrentTradeCompletionTime();
967
968 m_TradeRow.m_Trade.T_LIFO = GetCurrentTradeIsLifo();
969}
970
971/*
972 * Select charge for the TRADE table from the input file.
973 *
974 * PARAMETERS:
975 * none
976 *
977 * RETURNS:
978 * none
979 *
980 */
981void CTradeGen::GenerateTradeCharge() {
982 unsigned int i;
983
984 // just scan sequentially for now
985 for (i = 0; i < m_ChargeFile.size(); ++i) {
986 const ChargeDataFileRecord &chargeRow = m_ChargeFile[i];
987 // search for the customer tier
988 if (GetCurrentCustTier() == chargeRow.CH_C_TIER()) {
989 const TradeTypeDataFileRecord &tradeTypeRow = m_TradeTypeFile[GetCurrentTradeType()];
990 // search for the trade type
991 // if (!strcmp(tradeTypeRow.TT_ID_CSTR(),
992 // chargeRow.CH_TT_ID_CSTR()))
993 if (0 == tradeTypeRow.TT_ID().compare(chargeRow.CH_TT_ID())) {
994 // found the correct charge
995 m_CompletedTradeInfo.Charge = chargeRow.CH_CHRG();
996
997 return;
998 }
999 }
1000 }
1001 // should never reach here
1002 assert(false);
1003}
1004
1005/*
1006 * Select commission for the TRADE table from the input file.
1007 *
1008 * PARAMETERS:
1009 * none
1010 *
1011 * RETURNS:
1012 * none
1013 *
1014 */
1015void CTradeGen::GenerateTradeCommission() {
1016 int iCustTier = GetCurrentCustTier();
1017 int iTradeQty = GetCurrentTradeQty();
1018 eTradeTypeID eTradeType = GetCurrentTradeType();
1019 eExchangeID eExchange = m_SecurityFile.GetExchangeIndex(GetCurrentSecurityIndex());
1020 const TradeTypeDataFileRecord &tradeTypeRow = m_TradeTypeFile[eTradeType];
1021 const SecurityDataFileRecord &securityRow = m_SecurityFile.GetRecord(GetCurrentSecurityIndex());
1022
1023 // Some extra logic to reduce looping in the CommissionRate file.
1024 // It is organized by tier, then trade type, then exchange.
1025 // Consider this extra knowledge to calculate the bounds of the search.
1026 //
1027 // Number of rows in the CommissionRate file with the same customer tier.
1028 //
1029 UINT iCustomerTierRecords = m_CommissionRateFile.size() / 3;
1030
1031 // Number of rows in the CommissionRate file with the same customer tier
1032 // AND trade type.
1033 //
1034 UINT iTradeTypeRecords = iCustomerTierRecords / m_TradeTypeFile.size();
1035
1036 // Number of rows in the CommissionRate file with the same customer tier
1037 // AND trade type AND exchange.
1038 //
1039 UINT iExchangeRecords = iTradeTypeRecords / m_ExchangeFile.size();
1040
1041 // Compute starting and ending bounds of scan
1042 UINT iStartIndex = ((iCustTier - 1) * iCustomerTierRecords) + ((int)eTradeType * iTradeTypeRecords) +
1043 ((int)eExchange * iExchangeRecords);
1044 UINT iEndIndex = iStartIndex + iExchangeRecords;
1045
1046 // Scan for the proper commission rate
1047 for (UINT i = iStartIndex; i < iEndIndex; i++) {
1048 const CommissionRateDataFileRecord &commissionRow = m_CommissionRateFile[i];
1049
1050 // sanity checking: tier, trade-type and exchange must match
1051 // otherwise; abort loop and fail
1052 if ((iCustTier != commissionRow.CR_C_TIER()) || (tradeTypeRow.TT_ID().compare(commissionRow.CR_TT_ID())) ||
1053 (securityRow.S_EX_ID().compare(commissionRow.CR_EX_ID()))) {
1054 break;
1055 }
1056
1057 // check for proper quantity
1058 if (iTradeQty >= commissionRow.CR_FROM_QTY() && iTradeQty <= commissionRow.CR_TO_QTY()) {
1059 // found the correct commission rate
1060 m_CompletedTradeInfo.Commission = (iTradeQty * GetCurrentTradePrice()) * commissionRow.CR_RATE() / 100.0;
1061 return;
1062 }
1063 }
1064
1065 // should never reach here
1066 assert(false);
1067}
1068
1069/*
1070 * Generate tax based on the account tax status and the tax rates for the
1071 * customer that owns the account.
1072 *
1073 * PARAMETERS:
1074 * none
1075 *
1076 * RETURNS:
1077 * none
1078 *
1079 */
1080void CTradeGen::GenerateTradeTax() {
1081 TIdent CustomerAD_ID;
1082 UINT iDivCode, iCtryCode;
1083 CMoney fProceeds;
1084 double fCtryRate, fDivRate;
1085
1086 // Check whether no capital gain exists and don't bother with calculations
1087 //
1088 if (GetCurrentTradeSellValue() <= GetCurrentTradeBuyValue()) {
1089 m_CompletedTradeInfo.Tax = 0;
1090 return;
1091 }
1092
1093 CustomerAD_ID = m_AddressTable.GetAD_IDForCustomer(GetCurrentCustID());
1094
1095 m_AddressTable.GetDivisionAndCountryCodesForAddress(CustomerAD_ID, iDivCode, iCtryCode);
1096
1097 fProceeds = GetCurrentTradeSellValue() - GetCurrentTradeBuyValue();
1098
1099 fCtryRate = m_CustTaxrateTable.GetCountryTaxRow(GetCurrentCustID(), iCtryCode).TX_RATE();
1100 fDivRate = m_CustTaxrateTable.GetDivisionTaxRow(GetCurrentCustID(), iDivCode).TX_RATE();
1101
1102 // Do a trick for proper rounding of resulting tax amount.
1103 // Txn rates (fCtryRate and fDivRate) have 4 digits after a floating point
1104 // so the existing CMoney class is not suitable to round them (because
1105 // CMoney only keeps 2 digits after the point). Therefore need to do the
1106 // manual trick of multiplying tax rates by 10000.0 (not 100.0), adding 0.5,
1107 // and truncating to int to get the proper rounding.
1108 //
1109 // This is all to match the database calculation of T_TAX done by runtime
1110 // transactions.
1111 //
1112 m_CompletedTradeInfo.Tax = fProceeds * ((double)((int)(10000.0 * (fCtryRate + fDivRate) + 0.5)) / 10000.0);
1113}
1114
1115/*
1116 * Generate complete TRADE_HISTORY row(s) information.
1117 *
1118 * PARAMETERS:
1119 * none
1120 *
1121 * RETURNS:
1122 * none
1123 *
1124 */
1125void CTradeGen::GenerateTradeHistoryRow() {
1126 if (GetCurrentTradeType() == eStopLoss || GetCurrentTradeType() == eLimitSell ||
1127 GetCurrentTradeType() == eLimitBuy) {
1128 m_iTradeHistoryRowCount = 3; // insert 3 rows
1129
1130 // insert Pending record
1131 m_TradeRow.m_TradeHistory[0].TH_T_ID = GetCurrentTradeID();
1132 strncpy(m_TradeRow.m_TradeHistory[0].TH_ST_ID, m_StatusTypeFile[ePending].ST_ID_CSTR(),
1133 sizeof(m_TradeRow.m_TradeHistory[0].TH_ST_ID));
1134 m_TradeRow.m_TradeHistory[0].TH_DTS = GetCurrentTradePendingTime();
1135
1136 // insert Submitted record
1137 m_TradeRow.m_TradeHistory[1].TH_T_ID = GetCurrentTradeID();
1138 strncpy(m_TradeRow.m_TradeHistory[1].TH_ST_ID, m_StatusTypeFile[eSubmitted].ST_ID_CSTR(),
1139 sizeof(m_TradeRow.m_TradeHistory[1].TH_ST_ID));
1140 m_TradeRow.m_TradeHistory[1].TH_DTS = GetCurrentTradeSubmissionTime();
1141
1142 // insert Completed record
1143 m_TradeRow.m_TradeHistory[2].TH_T_ID = GetCurrentTradeID();
1144 strncpy(m_TradeRow.m_TradeHistory[2].TH_ST_ID, m_StatusTypeFile[eCompleted].ST_ID_CSTR(),
1145 sizeof(m_TradeRow.m_TradeHistory[2].TH_ST_ID));
1146 m_TradeRow.m_TradeHistory[2].TH_DTS = GetCurrentTradeCompletionTime();
1147 } else {
1148 m_iTradeHistoryRowCount = 2; // insert 2 rows
1149
1150 // insert Submitted record
1151 m_TradeRow.m_TradeHistory[0].TH_T_ID = GetCurrentTradeID();
1152 strncpy(m_TradeRow.m_TradeHistory[0].TH_ST_ID, m_StatusTypeFile[eSubmitted].ST_ID_CSTR(),
1153 sizeof(m_TradeRow.m_TradeHistory[0].TH_ST_ID));
1154 m_TradeRow.m_TradeHistory[0].TH_DTS = GetCurrentTradeSubmissionTime();
1155
1156 // insert Completed record
1157 m_TradeRow.m_TradeHistory[1].TH_T_ID = GetCurrentTradeID();
1158 strncpy(m_TradeRow.m_TradeHistory[1].TH_ST_ID, m_StatusTypeFile[eCompleted].ST_ID_CSTR(),
1159 sizeof(m_TradeRow.m_TradeHistory[1].TH_ST_ID));
1160 m_TradeRow.m_TradeHistory[1].TH_DTS = GetCurrentTradeCompletionTime();
1161 }
1162}
1163
1164/*
1165 * Generate settlement amount for the current trade (value to use for SE_AMT
1166 * and CT_AMT).
1167 *
1168 * PARAMETERS:
1169 * none
1170 *
1171 * RETURNS:
1172 * none
1173 *
1174 */
1175void CTradeGen::GenerateSettlementAmount() {
1176 // Settlement amount calculation matching Trade Result Frame 6.
1177 //
1178 if (m_TradeTypeFile[GetCurrentTradeType()].TT_IS_SELL()) {
1179 m_CompletedTradeInfo.SettlementAmount = GetCurrentTradeQty() * GetCurrentTradePrice() -
1180 m_CompletedTradeInfo.Charge - m_CompletedTradeInfo.Commission;
1181 } else {
1182 m_CompletedTradeInfo.SettlementAmount = -1 * (GetCurrentTradeQty() * GetCurrentTradePrice() +
1183 m_CompletedTradeInfo.Charge + m_CompletedTradeInfo.Commission);
1184 }
1185
1186 switch (GetCurrentTaxStatus()) {
1187 case eNonTaxable: // no taxes
1188 break;
1189 case eTaxableAndWithhold: // calculate and withhold
1190 m_CompletedTradeInfo.SettlementAmount -= m_CompletedTradeInfo.Tax;
1191 break;
1192 case eTaxableAndDontWithhold: // calculate and do not withhold
1193 break;
1194 default: // should never happen
1195 assert(false);
1196 }
1197}
1198
1199/*
1200 * Generate complete CASH_TRANSACTION row information.
1201 *
1202 * PARAMETERS:
1203 * none
1204 *
1205 * RETURNS:
1206 * none
1207 *
1208 */
1209void CTradeGen::GenerateCashTransactionRow() {
1210 char S_NAME[cS_NAME_len + 1];
1211
1212 if (GetCurrentTradeIsCash()) {
1213 m_iCashTransactionRowCount = 1;
1214
1215 m_TradeRow.m_CashTransaction.CT_DTS = GetCurrentTradeCompletionTime();
1216 m_TradeRow.m_CashTransaction.CT_T_ID = GetCurrentTradeID();
1217 m_TradeRow.m_CashTransaction.CT_AMT = GetCurrentSettlementAmount().DollarAmount();
1218
1219 m_SecurityTable.CreateName(GetCurrentSecurityIndex(), S_NAME, static_cast<int>(sizeof(S_NAME)));
1220
1221 snprintf(m_TradeRow.m_CashTransaction.CT_NAME, sizeof(m_TradeRow.m_CashTransaction.CT_NAME),
1222 "%s %d shares of %s", m_TradeTypeFile[GetCurrentTradeType()].TT_NAME_CSTR(), GetCurrentTradeQty(),
1223 S_NAME);
1224 } else {
1225 m_iCashTransactionRowCount = 0; // no rows to insert
1226 }
1227}
1228
1229/*
1230 * Generate complete SETTLEMENT row information.
1231 *
1232 * PARAMETERS:
1233 * none
1234 *
1235 * RETURNS:
1236 * none
1237 *
1238 */
1239void CTradeGen::GenerateSettlementRow() {
1240 m_iSettlementRowCount = 1;
1241
1242 m_TradeRow.m_Settlement.SE_T_ID = GetCurrentTradeID();
1243
1244 if (GetCurrentTradeIsCash()) {
1245 strncpy(m_TradeRow.m_Settlement.SE_CASH_TYPE, "Cash Account", sizeof(m_TradeRow.m_Settlement.SE_CASH_TYPE));
1246 } else {
1247 strncpy(m_TradeRow.m_Settlement.SE_CASH_TYPE, "Margin", sizeof(m_TradeRow.m_Settlement.SE_CASH_TYPE));
1248 }
1249
1250 m_TradeRow.m_Settlement.SE_CASH_DUE_DATE = GetCurrentTradeCompletionTime();
1251 m_TradeRow.m_Settlement.SE_CASH_DUE_DATE.Add(2, 0); // add two days
1252 m_TradeRow.m_Settlement.SE_CASH_DUE_DATE.Set(0, 0, 0,
1253 0); // zero out time portion
1254 m_TradeRow.m_Settlement.SE_AMT = GetCurrentSettlementAmount().DollarAmount();
1255}
1256