| 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, Doug Johnson |
| 35 | */ |
| 36 | |
| 37 | /****************************************************************************** |
| 38 | * Description: Implementation of the MEESecurity class. |
| 39 | * See MEESecurity.h for a description. |
| 40 | ******************************************************************************/ |
| 41 | |
| 42 | #include <cmath> |
| 43 | #include "main/MEESecurity.h" |
| 44 | |
| 45 | using namespace TPCE; |
| 46 | |
| 47 | // Period of security price change (in seconds) |
| 48 | // e.g. when the price will repeat. |
| 49 | // |
| 50 | const int iSecPricePeriod = 900; // 15 minutes |
| 51 | |
| 52 | // Mean delay between Submission and Completion times |
| 53 | // |
| 54 | const double fMeanCompletionTimeDelay = 1.0; |
| 55 | |
| 56 | // Delay added to the clipped MEE Completion delay |
| 57 | // to simulate SUT-to-MEE and MEE-to-SUT processing delays. |
| 58 | // |
| 59 | const double fCompletionSUTDelay = 1.0; // seconds |
| 60 | |
| 61 | /* |
| 62 | * Constructor. |
| 63 | * Initializes the class using RNG and constants. |
| 64 | * |
| 65 | * PARAMETERS: |
| 66 | * none. |
| 67 | * |
| 68 | * RETURNS: |
| 69 | * not applicable. |
| 70 | */ |
| 71 | CMEESecurity::CMEESecurity() |
| 72 | : m_rnd(RNGSeedBaseMEESecurity), m_fRangeLow(fMinSecPrice), m_fRangeHigh(fMaxSecPrice), |
| 73 | m_fRange(fMaxSecPrice - fMinSecPrice), m_iPeriod(iSecPricePeriod), m_TradingTimeSoFar(0), m_pBaseTime(NULL), |
| 74 | m_pCurrentTime(NULL) { |
| 75 | } |
| 76 | |
| 77 | /* |
| 78 | * Initialize before the first use. |
| 79 | * Separated from constructor in order to have default (no-parameters) |
| 80 | * constructor. |
| 81 | * |
| 82 | * PARAMETERS: |
| 83 | * IN Index - unique security index to |
| 84 | * generate a unique starting price IN TradeTimeSoFar - point |
| 85 | * where we last left off on the price curve IN pBaseTime - wall clock time |
| 86 | * corresponding to the initial time for all securities IN pCurrentTime - |
| 87 | * current time for the security (determines current price) IN |
| 88 | * fMeanInTheMoneySubmissionDelay - Mean delay between Pending and Submission |
| 89 | * times for an immediatelly triggered (in-the-money) limit order. |
| 90 | * |
| 91 | * RETURNS: |
| 92 | * none |
| 93 | */ |
| 94 | void CMEESecurity::Init(INT32 TradingTimeSoFar, // for picking up where we last |
| 95 | // left off on the price curve |
| 96 | CDateTime *pBaseTime, CDateTime *pCurrentTime, |
| 97 | |
| 98 | // Mean delay between Pending and Submission times |
| 99 | // for an immediatelly triggered (in-the-money) limit |
| 100 | // order. |
| 101 | // |
| 102 | // The actual delay is randomly calculated in the range |
| 103 | // [0.5 * Mean .. 1.5 * Mean] |
| 104 | // |
| 105 | double fMeanInTheMoneySubmissionDelay) { |
| 106 | m_TradingTimeSoFar = TradingTimeSoFar; |
| 107 | m_pBaseTime = pBaseTime; |
| 108 | m_pCurrentTime = pCurrentTime; |
| 109 | |
| 110 | m_fMeanInTheMoneySubmissionDelay = fMeanInTheMoneySubmissionDelay; |
| 111 | |
| 112 | // Reset the RNG seed so that loading in multiple EGenLoader instances |
| 113 | // results in the same database as running just one EGenLoader. |
| 114 | // |
| 115 | m_rnd.SetSeed(RNGSeedBaseMEESecurity); |
| 116 | } |
| 117 | |
| 118 | /* |
| 119 | * Calculate the "unique" starting offset |
| 120 | * in the price curve based on the security ID (0-based) |
| 121 | * 0 corresponds to m_fRangeLow price, |
| 122 | * m_fPeriod/2 corresponds to m_fRangeHigh price, |
| 123 | * m_fPeriod corresponds again to m_fRangeLow price |
| 124 | * |
| 125 | * PARAMETERS: |
| 126 | * IN SecurityIndex - unique security index to generate a unique |
| 127 | * starting price |
| 128 | * |
| 129 | * RETURNS: |
| 130 | * time from which to calculate initial price |
| 131 | */ |
| 132 | inline double CMEESecurity::InitialTime(TIdent SecurityIndex) { |
| 133 | INT32 MsPerPeriod = iSecPricePeriod * MsPerSecond; |
| 134 | TIdent SecurityFactor = SecurityIndex * 556237 + 253791; |
| 135 | TIdent TradingFactor = (TIdent)m_TradingTimeSoFar * MsPerSecond; // Cast to avoid truncation |
| 136 | |
| 137 | return (((TradingFactor + SecurityFactor) % MsPerPeriod) / MsPerSecondDivisor); |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | * Negative exponential distribution. |
| 142 | * |
| 143 | * PARAMETERS: |
| 144 | * IN fMean - mean value of the distribution |
| 145 | * |
| 146 | * RETURNS: |
| 147 | * random value according to the negative |
| 148 | * exponential distribution with the given mean. |
| 149 | */ |
| 150 | inline double CMEESecurity::NegExp(double fMean) { |
| 151 | return RoundToNearestNsec(m_rnd.RndDoubleNegExp(fMean)); |
| 152 | } |
| 153 | |
| 154 | /* |
| 155 | * Calculate current price for the security identified by its index (0-based). |
| 156 | * |
| 157 | * PARAMETERS: |
| 158 | * IN SecurityIndex - unique identifier for the security. |
| 159 | * |
| 160 | * RETURNS: |
| 161 | * price at this point in time given with integer number of cents. |
| 162 | */ |
| 163 | CMoney CMEESecurity::GetCurrentPrice(TIdent SecurityIndex) { |
| 164 | return (CalculatePrice(SecurityIndex, *m_pCurrentTime - *m_pBaseTime)); |
| 165 | } |
| 166 | |
| 167 | /* |
| 168 | * Return minimum price on the price curve for any security. |
| 169 | * |
| 170 | * PARAMETERS: |
| 171 | * none. |
| 172 | * |
| 173 | * RETURNS: |
| 174 | * minimum price given with integer number of cents. |
| 175 | */ |
| 176 | CMoney CMEESecurity::GetMinPrice(void) { |
| 177 | return (m_fRangeLow); |
| 178 | } |
| 179 | |
| 180 | /* |
| 181 | * Return maximum price on the price curve for any security. |
| 182 | * |
| 183 | * PARAMETERS: |
| 184 | * none. |
| 185 | * |
| 186 | * RETURNS: |
| 187 | * maximum price given with integer number of cents. |
| 188 | */ |
| 189 | CMoney CMEESecurity::GetMaxPrice(void) { |
| 190 | return (m_fRangeHigh); |
| 191 | } |
| 192 | |
| 193 | /* |
| 194 | * Calculate price at a certain point in time. |
| 195 | * |
| 196 | * PARAMETERS: |
| 197 | * IN SecurityIndex - unique security index to generate a unique |
| 198 | * starting price IN fTime - seconds from initial time |
| 199 | * |
| 200 | * RETURNS: |
| 201 | * price according to the triangular function |
| 202 | * that will be achived at the given time |
| 203 | */ |
| 204 | CMoney CMEESecurity::CalculatePrice(TIdent SecurityIndex, double fTime) { |
| 205 | double fPeriodTime = (fTime + InitialTime(SecurityIndex)) / (double)m_iPeriod; |
| 206 | double fTimeWithinPeriod = (fPeriodTime - (int)fPeriodTime) * (double)m_iPeriod; |
| 207 | |
| 208 | double fPricePosition; // 0..1 corresponding to m_fRangeLow..m_fRangeHigh |
| 209 | CMoney PriceCents; |
| 210 | |
| 211 | if (fTimeWithinPeriod < m_iPeriod / 2) { |
| 212 | fPricePosition = fTimeWithinPeriod / (m_iPeriod / 2); |
| 213 | } else { |
| 214 | fPricePosition = (m_iPeriod - fTimeWithinPeriod) / (m_iPeriod / 2); |
| 215 | } |
| 216 | |
| 217 | PriceCents = m_fRangeLow + m_fRange * fPricePosition; |
| 218 | |
| 219 | return PriceCents; |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | * Calculate time required to move between certain prices |
| 224 | * with certain initial direction of price change. |
| 225 | * |
| 226 | * PARAMETERS: |
| 227 | * IN fStartPrice - price at the start of the time interval |
| 228 | * IN fEndPrice - price at the end of the time interval |
| 229 | * IN iStartDirection - direction (up or down) on the price curve at |
| 230 | * the start of the time interval |
| 231 | * |
| 232 | * RETURNS: |
| 233 | * seconds required to move from the start price to the end price |
| 234 | */ |
| 235 | double CMEESecurity::CalculateTime(CMoney fStartPrice, CMoney fEndPrice, int iStartDirection) { |
| 236 | int iHalfPeriod = m_iPeriod / 2; |
| 237 | |
| 238 | // Distance on the price curve from StartPrice to EndPrice (in dollars) |
| 239 | // |
| 240 | CMoney fDistance; |
| 241 | |
| 242 | // Amount of time (in seconds) needed to move $1 on the price curve. |
| 243 | // In half a period the price moves over the entire price range. |
| 244 | // |
| 245 | double fSpeed = iHalfPeriod / m_fRange.DollarAmount(); |
| 246 | |
| 247 | if (fEndPrice > fStartPrice) { |
| 248 | if (iStartDirection > 0) { |
| 249 | fDistance = fEndPrice - fStartPrice; |
| 250 | } else { |
| 251 | fDistance = (fStartPrice - m_fRangeLow) + (fEndPrice - m_fRangeLow); |
| 252 | } |
| 253 | } else { |
| 254 | if (iStartDirection > 0) { |
| 255 | fDistance = (m_fRangeHigh - fStartPrice) + (m_fRangeHigh - fEndPrice); |
| 256 | } else { |
| 257 | fDistance = fStartPrice - fEndPrice; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | return fDistance.DollarAmount() * fSpeed; |
| 262 | } |
| 263 | |
| 264 | /* |
| 265 | * Calculate triggering time for limit orders. |
| 266 | * |
| 267 | * PARAMETERS: |
| 268 | * IN SecurityIndex - unique security index to generate a unique |
| 269 | * starting price IN fPendingTime - pending time of the order, in seconds |
| 270 | * from time 0 IN fLimitPrice - limit price of the order IN TradeType - |
| 271 | * order trade type |
| 272 | * |
| 273 | * RETURNS: |
| 274 | * the expected submission time |
| 275 | */ |
| 276 | double CMEESecurity::GetSubmissionTime(TIdent SecurityIndex, |
| 277 | double fPendingTime, // in seconds from time 0 |
| 278 | CMoney fLimitPrice, eTradeTypeID TradeType) { |
| 279 | CMoney fPriceAtPendingTime = CalculatePrice(SecurityIndex, fPendingTime); |
| 280 | |
| 281 | int iDirectionAtPendingTime; |
| 282 | |
| 283 | double fSubmissionTimeFromPending; // Submission - Pending time difference |
| 284 | |
| 285 | // Check if the order is already in the money |
| 286 | // e.g. if the current price is less than the buy price |
| 287 | // or the current price is more than the sell price. |
| 288 | // |
| 289 | if (((TradeType == eLimitBuy || TradeType == eStopLoss) && fPriceAtPendingTime <= fLimitPrice) || |
| 290 | ((TradeType == eLimitSell) && fPriceAtPendingTime >= fLimitPrice)) { |
| 291 | // Order is in-the-money. Trigger immediatelly. |
| 292 | // |
| 293 | fSubmissionTimeFromPending = m_rnd.RndDoubleIncrRange(0.5 * m_fMeanInTheMoneySubmissionDelay, |
| 294 | 1.5 * m_fMeanInTheMoneySubmissionDelay, 0.001); |
| 295 | } else { |
| 296 | if ((int)(fPendingTime + InitialTime(SecurityIndex)) % m_iPeriod < m_iPeriod / 2) { |
| 297 | // In the first half of the period => price is going up |
| 298 | // |
| 299 | iDirectionAtPendingTime = 1; |
| 300 | } else { |
| 301 | // In the second half of the period => price is going down |
| 302 | // |
| 303 | iDirectionAtPendingTime = -1; |
| 304 | } |
| 305 | |
| 306 | fSubmissionTimeFromPending = CalculateTime(fPriceAtPendingTime, fLimitPrice, iDirectionAtPendingTime); |
| 307 | } |
| 308 | |
| 309 | return fPendingTime + fSubmissionTimeFromPending; |
| 310 | } |
| 311 | |
| 312 | /* |
| 313 | * Return the expected completion time and the completion price. |
| 314 | * Completion time is between 0 and 5 seconds |
| 315 | * with 1 sec mean. |
| 316 | * |
| 317 | * Used to calculate completion time for |
| 318 | * both limit (first must get submission time) |
| 319 | * and market orders. |
| 320 | * |
| 321 | * Equivalent of MEE function sequence |
| 322 | * 'receive trade' then 'complete the trade request'. |
| 323 | * |
| 324 | * PARAMETERS: |
| 325 | * IN SecurityIndex - unique security index to generate a |
| 326 | * unique starting price IN fSubmissionTime - time when the order was |
| 327 | * submitted, in seconds from time 0 OUT pCompletionPrice - completion price |
| 328 | * of the order |
| 329 | * |
| 330 | * RETURNS: |
| 331 | * the approximated completion time for the trade |
| 332 | * |
| 333 | */ |
| 334 | double CMEESecurity::GetCompletionTime(TIdent SecurityIndex, |
| 335 | double fSubmissionTime, // in seconds from time 0 |
| 336 | CMoney *pCompletionPrice // out |
| 337 | ) { |
| 338 | double fCompletionDelay = NegExp(fMeanCompletionTimeDelay); |
| 339 | |
| 340 | // Clip at 5 seconds to prevent rare, but really long delays |
| 341 | // |
| 342 | if (fCompletionDelay > 5.0) { |
| 343 | fCompletionDelay = 5.0; |
| 344 | } |
| 345 | |
| 346 | if (pCompletionPrice != NULL) { |
| 347 | *pCompletionPrice = CalculatePrice(SecurityIndex, fSubmissionTime + fCompletionDelay); |
| 348 | } |
| 349 | |
| 350 | return fSubmissionTime + fCompletionDelay + fCompletionSUTDelay; |
| 351 | } |
| 352 | |