/* * Legal Notice * * This document and associated source code (the "Work") is a part of a * benchmark specification maintained by the TPC. * * The TPC reserves all right, title, and interest to the Work as provided * under U.S. and international laws, including without limitation all patent * and trademark rights therein. * * No Warranty * * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION * CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE * AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER * WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, * INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES, * DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR * PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF * WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE. * ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, * QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT * WITH REGARD TO THE WORK. * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO * ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE * COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS * OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT, * INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY, * OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT * RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD * ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. * * Contributors * - Sergey Vasilevskiy * - Doug Johnson */ #include "main/EGenTables_stdafx.h" #include "main/ExchangeIDs.h" using namespace TPCE; // Price change function period in seconds // const int iSecPricePeriod = 15 * SecondsPerMinute; // set to 15 minutes, in seconds // Number of RNG calls for one simulated trade const int iRNGSkipOneTrade = 11; // average count for v3.5: 6.5 // Operator for priority queue // namespace TPCE { // Need const reference left argument for greater comparison // function bool operator>(const TTradeInfo &l, const TTradeInfo &r) { return l.CompletionTime > r.CompletionTime; } } // namespace TPCE /* * Constructor. * Creates priority queue. * */ CTradeGen::CTradeGen(const DataFileManager &dfm, TIdent iCustomerCount, TIdent iStartFromCustomer, TIdent iTotalCustomers, UINT iLoadUnitSize, UINT iScaleFactor, UINT iHoursOfInitialTrades, bool bCacheEnabled) : m_rnd(RNGSeedTradeGen), m_AddressTable(dfm, iCustomerCount, iStartFromCustomer, true, bCacheEnabled) // only customer addresses , m_CustomerSelection(&m_rnd, 0, 0, 100, iStartFromCustomer, iLoadUnitSize) // only generate customer within partition , m_CustomerTable(dfm, iCustomerCount, iStartFromCustomer), m_CustTaxrateTable(dfm, iCustomerCount, iStartFromCustomer, bCacheEnabled), m_CustomerAccountTable(dfm, iLoadUnitSize, iCustomerCount, iStartFromCustomer, bCacheEnabled), m_HoldingTable(dfm, iLoadUnitSize, iCustomerCount, iStartFromCustomer, bCacheEnabled), m_BrokerTable(dfm, iCustomerCount, iStartFromCustomer), m_SecurityTable(dfm, iCustomerCount, iStartFromCustomer), m_Person(dfm, iStartFromCustomer, bCacheEnabled), m_CompanyFile(dfm.CompanyFile()), m_SecurityFile(dfm.SecurityFile()), m_ChargeFile(dfm.ChargeDataFile()), m_CommissionRateFile(dfm.CommissionRateDataFile()), m_StatusTypeFile(dfm.StatusTypeDataFile()), m_TradeTypeFile(dfm.TradeTypeDataFile()), m_ExchangeFile(dfm.ExchangeDataFile()), m_iStartFromCustomer(iStartFromCustomer + iTIdentShift), m_iCustomerCount(iCustomerCount), m_iTotalCustomers(iTotalCustomers), m_iLoadUnitSize(iLoadUnitSize), m_iLoadUnitAccountCount(iLoadUnitSize * iMaxAccountsPerCust), m_iScaleFactor(iScaleFactor), m_iHoursOfInitialTrades(iHoursOfInitialTrades), m_fMeanTimeBetweenTrades(100.0 / iAbortTrade * (double)iScaleFactor / iLoadUnitSize), m_fMeanInTheMoneySubmissionDelay(1.0), m_CurrentSimulatedTime(0), m_iCurrentCompletedTrades(0), m_iTotalTrades((TTrade)iHoursOfInitialTrades * SecondsPerHour * iLoadUnitSize / iScaleFactor), m_iCurrentInitiatedTrades(0), m_iTradesPerWorkDay(HoursPerWorkDay * SecondsPerHour * iLoadUnitSize / iScaleFactor * iAbortTrade / 100), m_MEESecurity(), m_iCurrentAccountForHolding(0), m_iCurrentSecurityForHolding(0), m_pCurrentSecurityHolding(), m_iCurrentAccountForHoldingSummary(0), m_iCurrentSecurityForHoldingSummary(-1) // incremented in FindNextHoldingList() , m_iCurrentLoadUnit(0) { RNGSEED RNGSkipCount; // Set the start time (time 0) to the base time m_StartTime.Set(InitialTradePopulationBaseYear, InitialTradePopulationBaseMonth, InitialTradePopulationBaseDay, InitialTradePopulationBaseHour, InitialTradePopulationBaseMinute, InitialTradePopulationBaseSecond, InitialTradePopulationBaseFraction); // Get the first account number // m_iStartFromAccount = m_CustomerAccountTable.GetStartingCA_ID(m_iStartFromCustomer); // Create an array of customer holding lists // m_pCustomerHoldings = new THoldingList[m_iLoadUnitAccountCount][iMaxSecuritiesPerAccount]; // Clear row structures // memset(&m_NewTrade, 0, sizeof(m_NewTrade)); memset(&m_TradeRow, 0, sizeof(m_TradeRow)); memset(&m_HoldingRow, 0, sizeof(m_HoldingRow)); memset(&m_HoldingSummaryRow, 0, sizeof(m_HoldingSummaryRow)); // Position trade id at the proper start of the sequence // m_iCurrentTradeId = (TTrade)m_iHoursOfInitialTrades * SecondsPerHour * (iStartFromCustomer - iDefaultStartFromCustomer) / m_iScaleFactor // divide after multiplication to // avoid integer truncation * iAbortTrade / 100 + iTTradeShift; // Initialize BROKER table // m_BrokerTable.InitForGen(iLoadUnitSize, m_iStartFromCustomer - iTIdentShift); RNGSkipCount = (RNGSEED)(m_iStartFromCustomer / m_iLoadUnitSize * m_iTotalTrades); m_rnd.SetSeed(m_rnd.RndNthElement(RNGSeedTradeGen, RNGSkipCount * iRNGSkipOneTrade)); m_HoldingTable.InitNextLoadUnit(RNGSkipCount, m_iStartFromAccount); // Initialize security price emulation m_MEESecurity.Init(0, NULL, NULL, m_fMeanInTheMoneySubmissionDelay); } /* * Destructor. * Frees any memory allocated in the constructor. */ CTradeGen::~CTradeGen() { if (m_pCustomerHoldings != NULL) { delete[] m_pCustomerHoldings; } } /* * Initialize next load unit for a series of * GenerateNextTrade/GenerateNextHolding calls. * * The first load unit doesn't have to be initalized. * * RETURNS: * true - if a new load unit could be found * false - if all load units have been processed */ bool CTradeGen::InitNextLoadUnit() { RNGSEED RNGSkipCount; ++m_iCurrentLoadUnit; m_iCurrentCompletedTrades = 0; // No need to empty holdings as they were emptied by // GenerateNextHolding calls. // delete[] m_pCustomerHoldings; // Create an array of customer holding lists // m_pCustomerHoldings = new THoldingList[m_iLoadUnitAccountCount][iMaxSecuritiesPerAccount]; m_iCurrentAccountForHolding = 0; m_iCurrentSecurityForHolding = 0; m_iCurrentAccountForHoldingSummary = 0; m_iCurrentSecurityForHoldingSummary = -1; m_iStartFromCustomer += m_iLoadUnitSize; m_iStartFromAccount = m_CustomerAccountTable.GetStartingCA_ID(m_iStartFromCustomer); m_CurrentSimulatedTime = 0; m_iCurrentInitiatedTrades = 0; m_BrokerTable.InitForGen(m_iLoadUnitSize, m_iStartFromCustomer - iTIdentShift); RNGSkipCount = (RNGSEED)(m_iStartFromCustomer / m_iLoadUnitSize * m_iTotalTrades); m_rnd.SetSeed(m_rnd.RndNthElement(RNGSeedTradeGen, RNGSkipCount * iRNGSkipOneTrade)); // Move customer range for the next load unit. // m_CustomerSelection.SetPartitionRange(m_iStartFromCustomer, m_iLoadUnitSize); m_HoldingTable.InitNextLoadUnit(RNGSkipCount, m_iStartFromAccount); m_Person.InitNextLoadUnit(); m_AddressTable.InitNextLoadUnit(); m_CustomerAccountTable.InitNextLoadUnit(); m_MEESecurity.Init(0, NULL, NULL, m_fMeanInTheMoneySubmissionDelay); // Clear row structures. // memset(&m_TradeRow, 0, sizeof(m_TradeRow)); memset(&m_HoldingRow, 0, sizeof(m_HoldingRow)); memset(&m_HoldingSummaryRow, 0, sizeof(m_HoldingSummaryRow)); return m_iCurrentLoadUnit < (m_iCustomerCount / m_iLoadUnitSize); } /* * Generate a new trade and all trade-related rows (except holdings). * * PARAMETERS: * none * * RETURNS: * true - if there are more trades * false - if there are no more trades to generate * */ bool CTradeGen::GenerateNextTrade() { bool bMoreTrades; if (m_iCurrentCompletedTrades < m_iTotalTrades) { // While the earliest completion time is before the current // simulated ('Trade Order') time, keep creating new // incomplete trades putting them on the queue and // incrementing the current simulated time. // while ((m_iCurrentCompletedTrades + (int)m_CurrentTrades.size() < m_iTotalTrades) && (m_CurrentTrades.empty() || (m_CurrentSimulatedTime < m_CurrentTrades.top().CompletionTime))) { m_CurrentSimulatedTime = (m_iCurrentInitiatedTrades / m_iTradesPerWorkDay) // number of days * SecondsPerDay // number of seconds in a day // now add the offset in the day + (m_iCurrentInitiatedTrades % m_iTradesPerWorkDay) * m_fMeanTimeBetweenTrades + GenerateDelayBetweenTrades(); // Generate new Trade Order; CMEESecurity // is used by this function. // GenerateNewTrade(); if (m_HoldingTable.IsAbortedTrade(m_iCurrentInitiatedTrades)) { // Abort trade and not put it into the queue. // Generate a new trade instead. // continue; } m_CurrentTrades.push(m_NewTrade); } // Get the earliest trade from the // front of the queue. // m_NewTrade = m_CurrentTrades.top(); // Remove the top element. // m_CurrentTrades.pop(); // Update HOLDING row for the customer // // Must be called before generating the complete trade // to calculate buy and sell values for T_TAX. // UpdateHoldings(); // Generate all the remaining trade data. // GenerateCompleteTrade(); bMoreTrades = m_iCurrentCompletedTrades < m_iTotalTrades; } else { bMoreTrades = false; } if (bMoreTrades) { return true; } else { // Before returning need to position // holding iterator for GenerateNextHolding() // m_pCurrentSecurityHolding = m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding].begin(); FindNextHolding(); // Set up for GenerateNextHoldingSummary FindNextHoldingList(); size_t iSize = m_CurrentTrades.size(); // info for debugging assert(iSize == 0); return false; } } /* * Generate a random delay in Submission (or Pending for limit trades) * time between two trades. * * PARAMETERS: * none * * RETURNS: * seconds of delay before simulated time for the next trade * */ inline double CTradeGen::GenerateDelayBetweenTrades() { // Return a random number between 0 and 1ms less than the mean. // return m_rnd.RndDoubleIncrRange(0.0, m_fMeanTimeBetweenTrades - 0.001, 0.001); } /* * Helper function to get the list of holdings * to modify after the last completed trade * * RETURNS: * reference to the list of holdings */ inline THoldingList *CTradeGen::GetHoldingListForCurrentTrade() { return &m_pCustomerHoldings[GetCurrentAccID() - m_iStartFromAccount][GetCurrentSecurityAccountIndex() - 1]; } /* * Helper function to get either the front or the end * of the holding list * * RETURNS: * iterator positined at the first element or at the last element */ list::iterator CTradeGen::PositionAtHoldingList(THoldingList *pHoldingList, int IsLifo) { if (pHoldingList->empty()) { return pHoldingList->end(); // iterator positioned after the last element } else { if (IsLifo) { return --(pHoldingList->end()); // position before the last element } else { return pHoldingList->begin(); } } } /* * Update holding information based on the trade row * internal structures. * In other words, update holdings for the last trade * that was generated (by GenerateCompleteTrade()). * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::UpdateHoldings() { THoldingList *pHoldingList; list::iterator pHolding; // start of the holding list int iNeededQty = GetCurrentTradeQty(); int iHoldQty; m_CompletedTradeInfo.fBuyValue = 0; m_CompletedTradeInfo.fSellValue = 0; // Start new series of HOLDING_HISTORY rows // m_iHoldingHistoryRowCount = 0; pHoldingList = GetHoldingListForCurrentTrade(); pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo()); if (GetCurrentTradeType() == eMarketBuy || GetCurrentTradeType() == eLimitBuy) { // Buy trade // Liquidate negative (short) holdings // while (!pHoldingList->empty() && pHolding->iTradeQty < 0 && iNeededQty > 0) { iHoldQty = pHolding->iTradeQty; pHolding->iTradeQty += iNeededQty; if (pHolding->iTradeQty > 0) { // Need to zero the qty for correct history row later // pHolding->iTradeQty = 0; // holding fully closed m_CompletedTradeInfo.fSellValue += -iHoldQty * pHolding->fTradePrice; m_CompletedTradeInfo.fBuyValue += -iHoldQty * GetCurrentTradePrice(); } else { m_CompletedTradeInfo.fSellValue += iNeededQty * pHolding->fTradePrice; m_CompletedTradeInfo.fBuyValue += iNeededQty * GetCurrentTradePrice(); } GenerateHoldingHistoryRow(pHolding->iTradeId, GetCurrentTradeID(), iHoldQty, pHolding->iTradeQty); if (pHolding->iTradeQty == 0) { // There was enough new quantity to fully close the old holding // pHolding = pHoldingList->erase(pHolding); // erase the old and return next holding } // Reposition at proper end // pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo()); iNeededQty += iHoldQty; } if (iNeededQty > 0) { // Still shares left after closing positions => create a new holding // THoldingInfo NewHolding = {GetCurrentTradeID(), iNeededQty, GetCurrentTradePrice(), GetCurrentTradeCompletionTime(), GetCurrentSecurityIndex()}; // Note: insert should be at the same end all the time // provided delete (PositionAtHoldingList()) is different // depending on IsLifo. // Vice versa also works - delete is at the // same end and insert depends on IsLifo. However, TradeResult // inserts at the end, so let loader insert in the same end. // pHoldingList->push_back(NewHolding); GenerateHoldingHistoryRow(GetCurrentTradeID(), GetCurrentTradeID(), 0, iNeededQty); } } else { // Sell trade // iNeededQty *= (-1); // make trade qty negative for convenience // Liquidate positive (long) holdings // while (!pHoldingList->empty() && pHolding->iTradeQty > 0 && iNeededQty > 0) { iHoldQty = pHolding->iTradeQty; pHolding->iTradeQty -= iNeededQty; if (pHolding->iTradeQty < 0) { // Need to zero the qty for correct history row later // pHolding->iTradeQty = 0; // holding fully closed m_CompletedTradeInfo.fSellValue += iHoldQty * GetCurrentTradePrice(); m_CompletedTradeInfo.fBuyValue += iHoldQty * pHolding->fTradePrice; } else { m_CompletedTradeInfo.fSellValue += iNeededQty * GetCurrentTradePrice(); m_CompletedTradeInfo.fBuyValue += iNeededQty * pHolding->fTradePrice; } GenerateHoldingHistoryRow(pHolding->iTradeId, GetCurrentTradeID(), iHoldQty, pHolding->iTradeQty); if (pHolding->iTradeQty == 0) { // There was enough new quantity to fully close the old holding // pHolding = pHoldingList->erase(pHolding); // erase the old and return next holding } // Reposition at proper end // pHolding = PositionAtHoldingList(pHoldingList, GetCurrentTradeIsLifo()); iNeededQty -= iHoldQty; } if (iNeededQty > 0) { // Still shares left after closing positions => create a new holding // THoldingInfo NewHolding = {GetCurrentTradeID(), -iNeededQty, GetCurrentTradePrice(), GetCurrentTradeCompletionTime(), GetCurrentSecurityIndex()}; // Note: insert should be at the same end all the time // provided delete (PositionAtHoldingList()) is different // depending on IsLifo. // Vice versa also works - delete is at the // same end and insert depends on IsLifo. However, TradeResult // inserts at the end, so let loader insert in the same end. // pHoldingList->push_back(NewHolding); GenerateHoldingHistoryRow(GetCurrentTradeID(), GetCurrentTradeID(), 0, -iNeededQty); } } } /* * Find next non-empty holding list. * * RETURNS: * whether a non-empty holding list exists */ bool CTradeGen::FindNextHoldingList() { THoldingList *pHoldingList; // Find the next non-empty holding list // do { m_iCurrentSecurityForHoldingSummary++; if (m_iCurrentSecurityForHoldingSummary == iMaxSecuritiesPerAccount) { // no more securities for the current account // so move on to next account. // m_iCurrentAccountForHoldingSummary++; m_iCurrentSecurityForHoldingSummary = 0; if (m_iCurrentAccountForHoldingSummary == m_iLoadUnitAccountCount) { // no more customers left, all holding lists have been processed // return false; } } // Set list pointer // pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHoldingSummary][m_iCurrentSecurityForHoldingSummary]; } while (pHoldingList->empty()); // test for empty HoldingList return true; } /* * Find non-empty holding and position internal * iterator at it. * * RETURNS: * whether a non-empty holding exists */ bool CTradeGen::FindNextHolding() { THoldingList *pHoldingList; pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding]; // Make sure the holding iterator points to a valid holding // do { if (m_pCurrentSecurityHolding == pHoldingList->end()) { // no more holding for the security => have to move to the next // security // ++m_iCurrentSecurityForHolding; if (m_iCurrentSecurityForHolding == iMaxSecuritiesPerAccount) { // no more holding for the account => have to move to the next // account // ++m_iCurrentAccountForHolding; m_iCurrentSecurityForHolding = 0; if (m_iCurrentAccountForHolding == m_iLoadUnitAccountCount) { // no more customers left => all holdings have been returned // return false; } } // Holding list has changed => reinitialize // pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHolding][m_iCurrentSecurityForHolding]; // Select the first holding in the new list // m_pCurrentSecurityHolding = pHoldingList->begin(); } } while (m_pCurrentSecurityHolding == pHoldingList->end()); // test for empty HoldingList return true; } /* * Generate a new HOLDING_SUMMARY row. * This function uses the lists of holdings generated * during simulated trade generation and returns the * row for the current account/security pair. * * PARAMETERS: * none * RETURNS: * true - if there are more holding lists * false - if there are no more holding lists */ bool CTradeGen::GenerateNextHoldingSummaryRow() { TIdent iSecurityFlatFileIndex; // index of the security in the input flat file if (m_iCurrentAccountForHoldingSummary < m_iLoadUnitAccountCount) { // There is always a valid holding list when this function // is called. The holding list to process is identified by // m_iCurrentAccountForHoldingSummary and // m_iCurrentSecurityForHoldingSummary. // m_HoldingSummaryRow.HS_CA_ID = m_iCurrentAccountForHoldingSummary + m_iStartFromAccount; iSecurityFlatFileIndex = m_HoldingTable.GetSecurityFlatFileIndex( m_iCurrentAccountForHoldingSummary + m_iStartFromAccount, (UINT)(m_iCurrentSecurityForHoldingSummary + 1)); m_SecurityFile.CreateSymbol(iSecurityFlatFileIndex, m_HoldingSummaryRow.HS_S_SYMB, static_cast(sizeof(m_HoldingSummaryRow.HS_S_SYMB))); // Sum up the quantities for the holding list THoldingList *pHoldingList; list::iterator pHolding; pHoldingList = &m_pCustomerHoldings[m_iCurrentAccountForHoldingSummary][m_iCurrentSecurityForHoldingSummary]; pHolding = pHoldingList->begin(); m_HoldingSummaryRow.HS_QTY = 0; while (pHolding != pHoldingList->end()) { m_HoldingSummaryRow.HS_QTY += pHolding->iTradeQty; pHolding++; } return FindNextHoldingList(); } else { return false; } } /* * Generate a new HOLDING_HISTORY row and update HOLDING_HISTORY row count. * * RETURNS: * none */ void CTradeGen::GenerateHoldingHistoryRow(TTrade iHoldingTradeID, // trade id of the original trade TTrade iTradeTradeID, // trade id of the modifying trade int iBeforeQty, // holding qty now int iAfterQty) // holding qty after modification { if (m_iHoldingHistoryRowCount < iMaxHoldingHistoryRowsPerTrade) { m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_H_T_ID = iHoldingTradeID; m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_T_ID = iTradeTradeID; m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_BEFORE_QTY = iBeforeQty; m_TradeRow.m_HoldingHistory[m_iHoldingHistoryRowCount].HH_AFTER_QTY = iAfterQty; ++m_iHoldingHistoryRowCount; } } /* * Generate a new holding row. * This function uses already prepared holding list structure * and returns the next holding for the current customer. * * The returned holding is deleted from the holding list * to clear the list for the next load unit. * * It moves to the next customer if the current one doesn't * have any more holdings. * * PARAMETERS: * none * RETURNS: * true - if there are more holdings * false - if there are no more holdings to return */ bool CTradeGen::GenerateNextHolding() { TIdent iSecurityFlatFileIndex; // index of the security in the input flat file if (m_iCurrentAccountForHolding < m_iLoadUnitAccountCount) { // There is always a valid holding when this function // is called. The holding to put into the HOLDING row // is pointed to by m_pCurrentSecurityHolding. // m_HoldingRow.H_CA_ID = m_iCurrentAccountForHolding + m_iStartFromAccount; // iSecurityFlatFileIndex = m_HoldingTable.GetSecurityFlatFileIndex( // m_iCurrentAccountForHolding // + m_iStartFromAccount, // m_iCurrentSecurityForHolding // + 1); iSecurityFlatFileIndex = m_pCurrentSecurityHolding->iSymbolIndex; m_SecurityFile.CreateSymbol(iSecurityFlatFileIndex, m_HoldingRow.H_S_SYMB, static_cast(sizeof(m_HoldingRow.H_S_SYMB))); m_HoldingRow.H_T_ID = m_pCurrentSecurityHolding->iTradeId; m_HoldingRow.H_QTY = m_pCurrentSecurityHolding->iTradeQty; m_HoldingRow.H_PRICE = m_pCurrentSecurityHolding->fTradePrice.DollarAmount(); m_HoldingRow.H_DTS = m_pCurrentSecurityHolding->BuyDTS; // Delete the holding and move to the next one in the account /*m_pCurrentSecurityHolding = m_pCustomerHoldings[m_iCurrentAccountForHolding] [m_iCurrentSecurityForHolding].erase(m_pCurrentSecurityHolding);*/ ++m_pCurrentSecurityHolding; // Move to the next valid holding for the next call. // return FindNextHolding(); } else { return false; } } /* * Generate a trade id for the next trade. * * PARAMETERS: * none * RETURNS: * a new unique trade id */ TTrade CTradeGen::GenerateNextTradeId() { return ++m_iCurrentTradeId; } /* * Generates a random trade type according to a certain distribution * * PARAMETERS: * none * RETURNS: * trade type id */ eTradeTypeID CTradeGen::GenerateTradeType() { eTradeTypeID eTradeType; // Generate Trade Type // NOTE: The order of these "if" tests is significant!! // Do not alter it unless you know what you are doing. // :-) // int iLoadTradeTypePct = m_rnd.RndGenerateIntegerPercentage(); if (iLoadTradeTypePct <= cMarketBuyLoadThreshold) // 1% - 30% { eTradeType = eMarketBuy; } else if (iLoadTradeTypePct <= cMarketSellLoadThreshold) // 31% - 60% { eTradeType = eMarketSell; } else if (iLoadTradeTypePct <= cLimitBuyLoadThreshold) // 61% - 80% { eTradeType = eLimitBuy; } else if (iLoadTradeTypePct <= cLimitSellLoadThreshold) // 81% - 90% { eTradeType = eLimitSell; } else if (iLoadTradeTypePct <= cStopLossLoadThreshold) // 91% - 100% { eTradeType = eStopLoss; } else { assert(false); // this should never happen } return eTradeType; } /* * Generate new incomplete trade (happens at Trade Order time) * with enough information to later generate a complete one. * * PARAMETERS: * none * * RETURNS: * none */ void CTradeGen::GenerateNewTrade() { m_NewTrade.iTradeId = GenerateNextTradeId(); // Select random customer // m_CustomerSelection.GenerateRandomCustomer(m_NewTrade.iCustomer, m_NewTrade.iCustomerTier); // Select random customer, account, and security within the account // m_HoldingTable.GenerateRandomAccountSecurity(m_NewTrade.iCustomer, m_NewTrade.iCustomerTier, &m_NewTrade.iCustomerAccount, &m_NewTrade.iSymbolIndex, &m_NewTrade.iSymbolIndexInAccount); m_NewTrade.eTradeType = GenerateTradeType(); // Status is always 'Completed' for initial trades // m_NewTrade.eTradeStatus = eCompleted; m_NewTrade.fBidPrice = m_rnd.RndDoubleIncrRange(fMinSecPrice, fMaxSecPrice, 0.01); m_NewTrade.iTradeQty = cTRADE_QTY_SIZES[m_rnd.RndIntRange(0, cNUM_TRADE_QTY_SIZES - 1)]; if (m_NewTrade.eTradeType == eMarketBuy || m_NewTrade.eTradeType == eMarketSell) { // A Market order // m_NewTrade.SubmissionTime = m_CurrentSimulatedTime; // Update the bid price to the current market price (like runtime) // m_NewTrade.fBidPrice = m_MEESecurity.CalculatePrice(m_NewTrade.iSymbolIndex, m_CurrentSimulatedTime); } else { // a Limit Order => need to calculate the Submission time // m_NewTrade.PendingTime = m_CurrentSimulatedTime; m_NewTrade.SubmissionTime = m_MEESecurity.GetSubmissionTime(m_NewTrade.iSymbolIndex, m_NewTrade.PendingTime, m_NewTrade.fBidPrice, m_NewTrade.eTradeType); // Move orders that would submit after market close (5pm) // to the beginning of the next day. // // Submission time here is kept from the beginning of the day, even // though it is later output to the database starting from 9am. So time // 0h corresponds to 9am, time 8hours corresponds to 5pm. // if ((((INT32)(m_NewTrade.SubmissionTime / SecondsPerHour)) % HoursPerDay == HoursPerWorkDay) && // >=5pm ((m_NewTrade.SubmissionTime / SecondsPerHour) - ((INT32)(m_NewTrade.SubmissionTime / SecondsPerHour)) > 0 // fractional seconds exist, e.g. not 5:00pm )) { m_NewTrade.SubmissionTime += 16 * SecondsPerHour; // add 16 hours to move to 9am next day } } // Calculate Completion time and price // m_NewTrade.CompletionTime = m_MEESecurity.GetCompletionTime(m_NewTrade.iSymbolIndex, m_NewTrade.SubmissionTime, &m_NewTrade.fTradePrice); // Make sure the trade has the right price based on the type of trade. if ((m_NewTrade.eTradeType == eLimitBuy && m_NewTrade.fBidPrice < m_NewTrade.fTradePrice) || (m_NewTrade.eTradeType == eLimitSell && m_NewTrade.fBidPrice > m_NewTrade.fTradePrice)) { m_NewTrade.fTradePrice = m_NewTrade.fBidPrice; } if (m_rnd.RndPercent(iPercentTradeIsLIFO)) { m_NewTrade.bIsLifo = true; } else { m_NewTrade.bIsLifo = false; } ++m_iCurrentInitiatedTrades; } /* * Generate a complete trade information * and fill all the internal row structures. * * A valid incomplete trade must exist in m_NewTrade. * * PARAMETERS: * none * * RETURNS: * none. * */ void CTradeGen::GenerateCompleteTrade() { GenerateCompletedTradeInfo(); GenerateTradeRow(); // TRADE row must be generated before all the others GenerateTradeHistoryRow(); GenerateCashTransactionRow(); GenerateSettlementRow(); m_BrokerTable.UpdateTradeAndCommissionYTD(GetCurrentBrokerId(), 1, m_TradeRow.m_Trade.T_COMM); ++m_iCurrentCompletedTrades; } /* * Generate frequently used fields for the completed trade. * This function must be called before generating individual * table rows. * * A valid incomplete trade must exist in m_NewTrade. * * PARAMETERS: * none * * RETURNS: * none. * */ void CTradeGen::GenerateCompletedTradeInfo() { m_CompletedTradeInfo.eAccountTaxStatus = m_CustomerAccountTable.GetAccountTaxStatus(GetCurrentAccID()); m_CompletedTradeInfo.iCurrentBrokerId = // not needed anymore? m_CustomerAccountTable.GenerateBrokerIdForAccount(GetCurrentAccID()); GenerateTradeCharge(); // generate charge GenerateTradeCommission(); // generate commission GenerateTradeTax(); GenerateSettlementAmount(); } /* * Generate complete TRADE row information. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateTradeRow() { m_TradeRow.m_Trade.T_ID = GetCurrentTradeID(); m_TradeRow.m_Trade.T_CA_ID = GetCurrentAccID(); strncpy(m_TradeRow.m_Trade.T_TT_ID, m_TradeTypeFile[GetCurrentTradeType()].TT_ID_CSTR(), sizeof(m_TradeRow.m_Trade.T_TT_ID)); strncpy(m_TradeRow.m_Trade.T_ST_ID, m_StatusTypeFile[GetCurrentTradeStatus()].ST_ID_CSTR(), sizeof(m_TradeRow.m_Trade.T_ST_ID)); // Generate whether the trade is cash trade. All sells are cash. 84% of buys // are cash. // m_TradeRow.m_Trade.T_IS_CASH = 1; // changed later if needed if (((GetCurrentTradeType() == eMarketBuy) || (GetCurrentTradeType() == eLimitBuy)) && m_rnd.RndPercent(iPercentBuysOnMargin)) { m_TradeRow.m_Trade.T_IS_CASH = 0; } snprintf(m_TradeRow.m_Trade.T_EXEC_NAME, sizeof(m_TradeRow.m_Trade.T_EXEC_NAME), "%s %s", m_Person.GetFirstName(GetCurrentCustID()).c_str(), m_Person.GetLastName(GetCurrentCustID()).c_str()); m_SecurityFile.CreateSymbol(GetCurrentSecurityIndex(), m_TradeRow.m_Trade.T_S_SYMB, static_cast(sizeof(m_TradeRow.m_Trade.T_S_SYMB))); m_TradeRow.m_Trade.T_BID_PRICE = GetCurrentBidPrice().DollarAmount(); m_TradeRow.m_Trade.T_TRADE_PRICE = GetCurrentTradePrice().DollarAmount(); m_TradeRow.m_Trade.T_QTY = GetCurrentTradeQty(); m_TradeRow.m_Trade.T_CHRG = m_CompletedTradeInfo.Charge.DollarAmount(); // get charge m_TradeRow.m_Trade.T_COMM = m_CompletedTradeInfo.Commission.DollarAmount(); // get commission // Get the tax amount. The check for positive capital gain is // in GenerateTradeTax(). If there is no capital gain, tax amount // will be set to zero by this time. // switch (GetCurrentTaxStatus()) { case eNonTaxable: // no taxes m_TradeRow.m_Trade.T_TAX = 0; break; case eTaxableAndWithhold: // calculate and withhold m_TradeRow.m_Trade.T_TAX = GetCurrentTradeTax().DollarAmount(); break; case eTaxableAndDontWithhold: // calculate and do not withhold m_TradeRow.m_Trade.T_TAX = GetCurrentTradeTax().DollarAmount(); break; default: // should never happen assert(false); } // T_DTS contains trade completion time. // m_TradeRow.m_Trade.T_DTS = GetCurrentTradeCompletionTime(); m_TradeRow.m_Trade.T_LIFO = GetCurrentTradeIsLifo(); } /* * Select charge for the TRADE table from the input file. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateTradeCharge() { unsigned int i; // just scan sequentially for now for (i = 0; i < m_ChargeFile.size(); ++i) { const ChargeDataFileRecord &chargeRow = m_ChargeFile[i]; // search for the customer tier if (GetCurrentCustTier() == chargeRow.CH_C_TIER()) { const TradeTypeDataFileRecord &tradeTypeRow = m_TradeTypeFile[GetCurrentTradeType()]; // search for the trade type // if (!strcmp(tradeTypeRow.TT_ID_CSTR(), // chargeRow.CH_TT_ID_CSTR())) if (0 == tradeTypeRow.TT_ID().compare(chargeRow.CH_TT_ID())) { // found the correct charge m_CompletedTradeInfo.Charge = chargeRow.CH_CHRG(); return; } } } // should never reach here assert(false); } /* * Select commission for the TRADE table from the input file. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateTradeCommission() { int iCustTier = GetCurrentCustTier(); int iTradeQty = GetCurrentTradeQty(); eTradeTypeID eTradeType = GetCurrentTradeType(); eExchangeID eExchange = m_SecurityFile.GetExchangeIndex(GetCurrentSecurityIndex()); const TradeTypeDataFileRecord &tradeTypeRow = m_TradeTypeFile[eTradeType]; const SecurityDataFileRecord &securityRow = m_SecurityFile.GetRecord(GetCurrentSecurityIndex()); // Some extra logic to reduce looping in the CommissionRate file. // It is organized by tier, then trade type, then exchange. // Consider this extra knowledge to calculate the bounds of the search. // // Number of rows in the CommissionRate file with the same customer tier. // UINT iCustomerTierRecords = m_CommissionRateFile.size() / 3; // Number of rows in the CommissionRate file with the same customer tier // AND trade type. // UINT iTradeTypeRecords = iCustomerTierRecords / m_TradeTypeFile.size(); // Number of rows in the CommissionRate file with the same customer tier // AND trade type AND exchange. // UINT iExchangeRecords = iTradeTypeRecords / m_ExchangeFile.size(); // Compute starting and ending bounds of scan UINT iStartIndex = ((iCustTier - 1) * iCustomerTierRecords) + ((int)eTradeType * iTradeTypeRecords) + ((int)eExchange * iExchangeRecords); UINT iEndIndex = iStartIndex + iExchangeRecords; // Scan for the proper commission rate for (UINT i = iStartIndex; i < iEndIndex; i++) { const CommissionRateDataFileRecord &commissionRow = m_CommissionRateFile[i]; // sanity checking: tier, trade-type and exchange must match // otherwise; abort loop and fail if ((iCustTier != commissionRow.CR_C_TIER()) || (tradeTypeRow.TT_ID().compare(commissionRow.CR_TT_ID())) || (securityRow.S_EX_ID().compare(commissionRow.CR_EX_ID()))) { break; } // check for proper quantity if (iTradeQty >= commissionRow.CR_FROM_QTY() && iTradeQty <= commissionRow.CR_TO_QTY()) { // found the correct commission rate m_CompletedTradeInfo.Commission = (iTradeQty * GetCurrentTradePrice()) * commissionRow.CR_RATE() / 100.0; return; } } // should never reach here assert(false); } /* * Generate tax based on the account tax status and the tax rates for the * customer that owns the account. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateTradeTax() { TIdent CustomerAD_ID; UINT iDivCode, iCtryCode; CMoney fProceeds; double fCtryRate, fDivRate; // Check whether no capital gain exists and don't bother with calculations // if (GetCurrentTradeSellValue() <= GetCurrentTradeBuyValue()) { m_CompletedTradeInfo.Tax = 0; return; } CustomerAD_ID = m_AddressTable.GetAD_IDForCustomer(GetCurrentCustID()); m_AddressTable.GetDivisionAndCountryCodesForAddress(CustomerAD_ID, iDivCode, iCtryCode); fProceeds = GetCurrentTradeSellValue() - GetCurrentTradeBuyValue(); fCtryRate = m_CustTaxrateTable.GetCountryTaxRow(GetCurrentCustID(), iCtryCode).TX_RATE(); fDivRate = m_CustTaxrateTable.GetDivisionTaxRow(GetCurrentCustID(), iDivCode).TX_RATE(); // Do a trick for proper rounding of resulting tax amount. // Txn rates (fCtryRate and fDivRate) have 4 digits after a floating point // so the existing CMoney class is not suitable to round them (because // CMoney only keeps 2 digits after the point). Therefore need to do the // manual trick of multiplying tax rates by 10000.0 (not 100.0), adding 0.5, // and truncating to int to get the proper rounding. // // This is all to match the database calculation of T_TAX done by runtime // transactions. // m_CompletedTradeInfo.Tax = fProceeds * ((double)((int)(10000.0 * (fCtryRate + fDivRate) + 0.5)) / 10000.0); } /* * Generate complete TRADE_HISTORY row(s) information. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateTradeHistoryRow() { if (GetCurrentTradeType() == eStopLoss || GetCurrentTradeType() == eLimitSell || GetCurrentTradeType() == eLimitBuy) { m_iTradeHistoryRowCount = 3; // insert 3 rows // insert Pending record m_TradeRow.m_TradeHistory[0].TH_T_ID = GetCurrentTradeID(); strncpy(m_TradeRow.m_TradeHistory[0].TH_ST_ID, m_StatusTypeFile[ePending].ST_ID_CSTR(), sizeof(m_TradeRow.m_TradeHistory[0].TH_ST_ID)); m_TradeRow.m_TradeHistory[0].TH_DTS = GetCurrentTradePendingTime(); // insert Submitted record m_TradeRow.m_TradeHistory[1].TH_T_ID = GetCurrentTradeID(); strncpy(m_TradeRow.m_TradeHistory[1].TH_ST_ID, m_StatusTypeFile[eSubmitted].ST_ID_CSTR(), sizeof(m_TradeRow.m_TradeHistory[1].TH_ST_ID)); m_TradeRow.m_TradeHistory[1].TH_DTS = GetCurrentTradeSubmissionTime(); // insert Completed record m_TradeRow.m_TradeHistory[2].TH_T_ID = GetCurrentTradeID(); strncpy(m_TradeRow.m_TradeHistory[2].TH_ST_ID, m_StatusTypeFile[eCompleted].ST_ID_CSTR(), sizeof(m_TradeRow.m_TradeHistory[2].TH_ST_ID)); m_TradeRow.m_TradeHistory[2].TH_DTS = GetCurrentTradeCompletionTime(); } else { m_iTradeHistoryRowCount = 2; // insert 2 rows // insert Submitted record m_TradeRow.m_TradeHistory[0].TH_T_ID = GetCurrentTradeID(); strncpy(m_TradeRow.m_TradeHistory[0].TH_ST_ID, m_StatusTypeFile[eSubmitted].ST_ID_CSTR(), sizeof(m_TradeRow.m_TradeHistory[0].TH_ST_ID)); m_TradeRow.m_TradeHistory[0].TH_DTS = GetCurrentTradeSubmissionTime(); // insert Completed record m_TradeRow.m_TradeHistory[1].TH_T_ID = GetCurrentTradeID(); strncpy(m_TradeRow.m_TradeHistory[1].TH_ST_ID, m_StatusTypeFile[eCompleted].ST_ID_CSTR(), sizeof(m_TradeRow.m_TradeHistory[1].TH_ST_ID)); m_TradeRow.m_TradeHistory[1].TH_DTS = GetCurrentTradeCompletionTime(); } } /* * Generate settlement amount for the current trade (value to use for SE_AMT * and CT_AMT). * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateSettlementAmount() { // Settlement amount calculation matching Trade Result Frame 6. // if (m_TradeTypeFile[GetCurrentTradeType()].TT_IS_SELL()) { m_CompletedTradeInfo.SettlementAmount = GetCurrentTradeQty() * GetCurrentTradePrice() - m_CompletedTradeInfo.Charge - m_CompletedTradeInfo.Commission; } else { m_CompletedTradeInfo.SettlementAmount = -1 * (GetCurrentTradeQty() * GetCurrentTradePrice() + m_CompletedTradeInfo.Charge + m_CompletedTradeInfo.Commission); } switch (GetCurrentTaxStatus()) { case eNonTaxable: // no taxes break; case eTaxableAndWithhold: // calculate and withhold m_CompletedTradeInfo.SettlementAmount -= m_CompletedTradeInfo.Tax; break; case eTaxableAndDontWithhold: // calculate and do not withhold break; default: // should never happen assert(false); } } /* * Generate complete CASH_TRANSACTION row information. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateCashTransactionRow() { char S_NAME[cS_NAME_len + 1]; if (GetCurrentTradeIsCash()) { m_iCashTransactionRowCount = 1; m_TradeRow.m_CashTransaction.CT_DTS = GetCurrentTradeCompletionTime(); m_TradeRow.m_CashTransaction.CT_T_ID = GetCurrentTradeID(); m_TradeRow.m_CashTransaction.CT_AMT = GetCurrentSettlementAmount().DollarAmount(); m_SecurityTable.CreateName(GetCurrentSecurityIndex(), S_NAME, static_cast(sizeof(S_NAME))); snprintf(m_TradeRow.m_CashTransaction.CT_NAME, sizeof(m_TradeRow.m_CashTransaction.CT_NAME), "%s %d shares of %s", m_TradeTypeFile[GetCurrentTradeType()].TT_NAME_CSTR(), GetCurrentTradeQty(), S_NAME); } else { m_iCashTransactionRowCount = 0; // no rows to insert } } /* * Generate complete SETTLEMENT row information. * * PARAMETERS: * none * * RETURNS: * none * */ void CTradeGen::GenerateSettlementRow() { m_iSettlementRowCount = 1; m_TradeRow.m_Settlement.SE_T_ID = GetCurrentTradeID(); if (GetCurrentTradeIsCash()) { strncpy(m_TradeRow.m_Settlement.SE_CASH_TYPE, "Cash Account", sizeof(m_TradeRow.m_Settlement.SE_CASH_TYPE)); } else { strncpy(m_TradeRow.m_Settlement.SE_CASH_TYPE, "Margin", sizeof(m_TradeRow.m_Settlement.SE_CASH_TYPE)); } m_TradeRow.m_Settlement.SE_CASH_DUE_DATE = GetCurrentTradeCompletionTime(); m_TradeRow.m_Settlement.SE_CASH_DUE_DATE.Add(2, 0); // add two days m_TradeRow.m_Settlement.SE_CASH_DUE_DATE.Set(0, 0, 0, 0); // zero out time portion m_TradeRow.m_Settlement.SE_AMT = GetCurrentSettlementAmount().DollarAmount(); }