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 | |
41 | using namespace TPCE; |
42 | |
43 | // Price change function period in seconds |
44 | // |
45 | const int iSecPricePeriod = 15 * SecondsPerMinute; // set to 15 minutes, in seconds |
46 | |
47 | // Number of RNG calls for one simulated trade |
48 | const int iRNGSkipOneTrade = 11; // average count for v3.5: 6.5 |
49 | |
50 | // Operator for priority queue |
51 | // |
52 | namespace TPCE { |
53 | |
54 | // Need const reference left argument for greater<TTradeInfo> comparison |
55 | // function |
56 | bool 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 | */ |
67 | CTradeGen::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 | */ |
148 | CTradeGen::~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 | */ |
164 | bool 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 | */ |
232 | bool 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 | */ |
322 | inline 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 | */ |
335 | inline 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 | */ |
346 | list<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 | */ |
372 | void 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 | */ |
508 | bool 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 | */ |
544 | bool 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 | */ |
598 | bool 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 | */ |
639 | void 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 | */ |
671 | bool 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 | */ |
719 | TTrade 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 | */ |
731 | eTradeTypeID 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 | */ |
772 | void 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 | */ |
857 | void 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 | */ |
884 | void 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 | */ |
909 | void 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 | */ |
981 | void 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 | */ |
1015 | void 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 | */ |
1080 | void 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 | */ |
1125 | void 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 | */ |
1175 | void 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 | */ |
1209 | void 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 | */ |
1239 | void 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 | |