/***************************************************************************
* Copyright (C) 2012 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Alex Kalinin (akalinin@cs.brown.edu) *
* http://www.cs.brown.edu/~akalinin/ *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.benchmark.tpce.generators;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import edu.brown.benchmark.tpce.TPCEConstants;
import edu.brown.benchmark.tpce.generators.TradeGenerator.TradeType;
import edu.brown.benchmark.tpce.util.EGenMoney;
import edu.brown.benchmark.tpce.util.EGenRandom;
/**
* This class is used to simulate monetary security exchanges
* both for the loader and for the driver *
*/
public class MEESecurity {
/*
* Period of security price change (in seconds)
* (e.g., when the price will repeat)
*/
private final static int SEC_PRICE_PERIOD = 900; // 15 minutes
// Mean delay between Submission and Completion times
private final static double MEAN_COMPLETION_TIME_DELAY = 1.0;
// Delay added to the clipped MEE Completion delay
// to simulate SUT-to-MEE and MEE-to-SUT processing delays.
private static final double COMPLETION_SUT_DELAY = 1.0;
private final EGenRandom rnd;
private final EGenMoney rangeLow;
private final EGenMoney rangeHigh;
private final EGenMoney range;
private final int period; // time to get to the same price (in seconds)
private int tradingTimeSoFar;
private Date baseTime, currTime;
private double meanInTheMoneySubmissionDelay;
public MEESecurity() {
rnd = new EGenRandom(EGenRandom.RNG_SEED_BASE_MEE_SECURITY);
rangeLow = new EGenMoney(TPCEConstants.minSecPrice);
rangeHigh = new EGenMoney(TPCEConstants.maxSecPrice);
range = new EGenMoney(TPCEConstants.maxSecPrice - TPCEConstants.minSecPrice);
period = SEC_PRICE_PERIOD; // time to get to the same price (in seconds)
}
/**
* Initializes (resets) the generator.
*
* @param tradingTimeSoFar time for picking up where we last left off on the price curve
* @param baseTime
* @param currentTime
* @param meanInTheMoneySubmissionDelay Mean delay between Pending and Submission times
* for an immediately triggered (in-the-money) limit order.
* The actual delay is randomly calculated in the range [0.5 * Mean .. 1.5 * Mean]
*/
public void init(int tradingTimeSoFar, Date baseTime, Date currentTime, double meanInTheMoneySubmissionDelay) {
this.tradingTimeSoFar = tradingTimeSoFar;
this.baseTime = baseTime;
this.currTime = currentTime;
this.meanInTheMoneySubmissionDelay = meanInTheMoneySubmissionDelay;
rnd.setSeed(EGenRandom.RNG_SEED_BASE_MEE_SECURITY);
}
/**
* Calculate the "unique" starting offset
* in the price curve based on the security ID (0-based)
* 0 corresponds to rangeLow price,
* period/2 corresponds to rangeHigh price,
* period corresponds again to rangeLow price
*
* @param secId unique security index to generate a unique starting price
* @return time from which to calculate initial price
*
*/
private double initialTime(long secId) {
return ((((tradingTimeSoFar * 1000) + (secId * 556237 + 253791)) % (SEC_PRICE_PERIOD * 1000)) / 1000.0); // 1000 here is for millisecs
}
public EGenMoney calculatePrice(long secId, double time) {
double periodTime = (time + initialTime(secId)) / period;
double timeWithinPeriod = (periodTime - (int)periodTime) * period;
double pricePosition; // 0..1 corresponding to rangeLow..rangeHigh
if (timeWithinPeriod < period / 2) {
pricePosition = timeWithinPeriod / (period / 2);
}
else {
pricePosition = (period - timeWithinPeriod) / (period / 2);
}
EGenMoney priceCents = new EGenMoney(range);
priceCents.multiplyByDouble(pricePosition);
priceCents.add(rangeLow);
return priceCents;
}
/**
* Calculate triggering time for limit orders.
*
* @param secIndex Unique security index to generate a unique starting price
* @param pendingTime Pending time of the order, in seconds from time 0
* @param limitPrice Limit price of the order
* @param tradeType Order trade type
*
* @return The expected submission time
*/
public double getSubmissionTime(long secIndex, double pendingTime, EGenMoney limitPrice, TradeType tradeType) {
EGenMoney priceAtPendingTime = calculatePrice(secIndex, pendingTime);
double submissionTimeFromPending; // Submission - Pending time difference
/*
* Check if the order can be fulfilled immediately
* i.e., if the current price is less than the buy price
* or the current price is more than the sell price.
*/
if (((tradeType == TradeType.eLimitBuy || tradeType == TradeType.eStopLoss) && priceAtPendingTime.lessThanOrEqual(limitPrice)) ||
((tradeType == TradeType.eLimitSell) && limitPrice.lessThanOrEqual(priceAtPendingTime))) {
// Trigger the order immediately.
submissionTimeFromPending = rnd.doubleRange(0.5 * meanInTheMoneySubmissionDelay, 1.5 * meanInTheMoneySubmissionDelay);
}
else {
int directionAtPendingTime;
if ((int)(pendingTime + initialTime(secIndex)) % period < period / 2) {
// In the first half of the period => price is going up
directionAtPendingTime = 1;
}
else {
// In the second half of the period => price is going down
directionAtPendingTime = -1;
}
submissionTimeFromPending = calculateTime(priceAtPendingTime, limitPrice, directionAtPendingTime);
}
return pendingTime + submissionTimeFromPending;
}
/**
* Calculate time required to move between certain prices
* with certain initial direction of price change.
*
* @param startPrice Price at the start of the time interval
* @param endPrice Price at the end of the time interval
* @param startDirection Direction (up or down) on the price curve at the start of the time interval
*
* @return Seconds required to move from the start price to the end price
*/
private double calculateTime(EGenMoney startPrice, EGenMoney endPrice, int startDirection) {
int halfPeriod = period / 2;
// Distance on the price curve from StartPrice to EndPrice (in dollars)
EGenMoney distance;
// Amount of time (in seconds) needed to move $1 on the price curve.
// In half a period the price moves over the entire price range.
double speed = halfPeriod / range.getDollars();
if (startPrice.lessThan(endPrice)) {
if (startDirection > 0) {
distance = EGenMoney.subMoney(endPrice, startPrice);
}
else {
distance = EGenMoney.addMoney(EGenMoney.subMoney(startPrice, rangeLow), EGenMoney.subMoney(endPrice, rangeLow));
}
}
else {
if (startDirection > 0) {
distance = EGenMoney.addMoney(EGenMoney.subMoney(rangeHigh, startPrice), EGenMoney.subMoney(rangeHigh, endPrice));
}
else {
distance = EGenMoney.subMoney(startPrice, endPrice);
}
}
return distance.getDollars() * speed;
}
/**
* Negative exponential distribution.
*
* PARAMETERS:
* IN fMean - mean value of the distribution
*
* RETURNS:
* random value according to the negative
* exponential distribution with the given mean.
*/
private double negExp(double mean) {
return (-1.0 * Math.log(rnd.rndDouble())) * mean;
}
/**
* Return the expected completion time and the completion price.
* Completion time is between 0 and 5 seconds
* with 1 sec mean.
*
* Used to calculate completion time for
* both limit (first must get submission time)
* and market orders.
*
* Equivalent of MEE function sequence
* 'receive trade' then 'complete the trade request'.
*
* @param secIndex Unique security index to generate a unique starting price
* @param submissionTime Time when the order was submitted, in seconds from time 0
*
* @return The approximated completion time for the trade and the price
*/
public Object[] getCompletionTimeAndPrice(long secIndex, double submissionTime) {
double completionDelay = negExp(MEAN_COMPLETION_TIME_DELAY);
// Clip at 5 seconds to prevent rare, but really long delays
if (completionDelay > 5.0) {
completionDelay = 5.0;
}
Object[] res = new Object[2];
res[0] = submissionTime + completionDelay + COMPLETION_SUT_DELAY;
res[1] = calculatePrice(secIndex, submissionTime + completionDelay);
return res;
}
public EGenMoney getMinPrice(){
return rangeLow;
}
public EGenMoney getMaxPrice(){
return rangeHigh;
}
public EGenMoney getCurrentPrice(long secId){
GregorianCalendar currGreTime = new GregorianCalendar();
GregorianCalendar baseGreTime = new GregorianCalendar();
currGreTime.setTime(currTime);
baseGreTime.setTime(baseTime);
return calculatePrice(secId, (currGreTime.getTimeInMillis() - baseGreTime.getTimeInMillis()) / 1000);
}
}