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
45using namespace TPCE;
46
47// Period of security price change (in seconds)
48// e.g. when the price will repeat.
49//
50const int iSecPricePeriod = 900; // 15 minutes
51
52// Mean delay between Submission and Completion times
53//
54const 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//
59const 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 */
71CMEESecurity::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 */
94void 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 */
132inline 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 */
150inline 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 */
163CMoney 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 */
176CMoney 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 */
189CMoney 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 */
204CMoney 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 */
235double 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 */
276double 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 */
334double 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