private static void ProcessSecurity(int securityId, int currencyId) { bool didDataChange = false; try { // Lock the tables. Debug.Assert(!ServerMarketData.IsLocked); ServerMarketData.PriceLock.AcquireWriterLock(Timeout.Infinite); ServerMarketData.SecurityLock.AcquireReaderLock(Timeout.Infinite); // get the price row associated with this security ServerMarketData.PriceRow priceRow = ServerMarketData.Price.FindBySecurityId(securityId); // if the row does not exist add it if (priceRow == null) { priceRow = ServerMarketData.Price.NewPriceRow(); //priceRow.RowVersion = ServerMarketData.RowVersion.Increment(); priceRow.SecurityId = securityId; priceRow.CurrencyId = currencyId; priceRow.LastPrice = 1.0M; priceRow.AskPrice = 1.01M; priceRow.BidPrice = 0.99M; priceRow.Volume = 0.0M; priceRow.VolumeWeightedAveragePrice = priceRow.LastPrice; priceRow.HighPrice = priceRow.LastPrice; priceRow.LowPrice = priceRow.LastPrice; priceRow.OpenPrice = priceRow.LastPrice; priceRow.ClosePrice = priceRow.LastPrice; ServerMarketData.Price.AddPriceRow(priceRow); openingPriceMap.Add(priceRow.SecurityId, priceRow.LastPrice); } // if we got a row then process it if (priceRow != null) { // get the opening price decimal openingPrice = priceRow.LastPrice; if (openingPriceMap.Contains(securityId)) { openingPrice = (decimal)openingPriceMap[securityId]; } if (openingPrice == 0) { openingPrice = 10; } // if we do not already have a volume or approx every 50th(ish) time update the volume if (priceRow.Volume <= 0 || (random.Next(50) == 0)) { // if this security ID is not already in the volume table pick a randon volume divergrance (-50% to 50%) // and add it to the table double volumeDivergence = 0.0; if (volumeDivergenceMap.Contains(securityId)) { volumeDivergence = (double)volumeDivergenceMap[securityId]; } else { volumeDivergence = (double)random.Next(-50, 50); } // increase or decrease by up to 1% int divergenceChangePercent = random.Next(-100, 100); // don't go over 50% up or down volumeDivergence = Math.Max(-50.0, Math.Min(50.0, volumeDivergence + ((double)divergenceChangePercent / 100.0))); // update the volume divergence table volumeDivergenceMap[securityId] = volumeDivergence; // get the estimated volume for this time of day decimal projectVolume = ProjectedVolume(DateTime.Now, priceRow.SecurityRow.AverageDailyVolume); // adjust it by the divergence we are using and update the price table priceRow.Volume = (int)(projectVolume - (int)((double)projectVolume * (volumeDivergence / 100.0))); didDataChange = true; } // base the frequency of price updates to the ADV of the security int updateFrequency = 10; if (priceRow.SecurityRow.AverageDailyVolume < 1000000) /* UNDER 1M about every 75 loops*/ { updateFrequency = 75; } else if (priceRow.SecurityRow.AverageDailyVolume < 5000000) /* 1M to 5M about every 30 loops */ { updateFrequency = 30; } else { updateFrequency = 20; /* OVER 5M about every 20 loops*/ } // about once every X loops update the price if (random.Next(updateFrequency) == 0) { // the anount of change will be based on stock price // < $10 == always 0.01 (plus or minus) // $10 - 25 == 0.01 to 0.02 (plus or minus) // $25 - 100 == 0.01 to 0.03 (plus or minus) // > $100 == 0.01 to 0.05 (plus or minus) double priceChange = 0; if (priceRow.LastPrice < 10) { priceChange = 0.01 - ((double)random.Next(3) * 0.01); } else if (priceRow.LastPrice < 25) { priceChange = 0.02 - ((double)random.Next(5) * 0.01); } else if (priceRow.LastPrice < 100) { priceChange = 0.03 - ((double)random.Next(7) * 0.01); } else { priceChange = 0.05 - ((double)random.Next(11) * 0.01); } // change the last price decimal newPrice = priceRow.LastPrice + (decimal)priceChange; decimal lastChange = openingPrice - newPrice; double lastChangepercent = ((double)(openingPrice - lastChange)) / (double)openingPrice; // don't let it go over 20% change per day if (Math.Abs(lastChangepercent) < 20) { priceRow.LastPrice = newPrice; } // set the spread to be 1 to 5 cents int spreadInCents = random.Next(1, 5); decimal spread = (decimal)spreadInCents / 100m; // set the bid to be 0 to (spread - 1) cents below the price priceRow.BidPrice = priceRow.LastPrice - ((decimal)random.Next(spreadInCents) / 100m); // set the ask based on the spread priceRow.AskPrice = priceRow.BidPrice + spread; // set the bid ask size priceRow.BidSize = random.Next(1, 999) * 100; priceRow.AskSize = random.Next(1, 999) * 100; // check to see if we are at a new high or low for the day if (priceRow.LastPrice > priceRow.HighPrice) { priceRow.HighPrice = priceRow.LastPrice; } if (priceRow.LastPrice < priceRow.LowPrice) { priceRow.LowPrice = priceRow.LastPrice; } // set the VWAP to be the average of the open and last // however don't let it be more than 5% from the last decimal newVwap = (priceRow.LastPrice + priceRow.OpenPrice) / 2; decimal fivePercentOfLast = priceRow.LastPrice * 0.05M; priceRow.VolumeWeightedAveragePrice = Math.Max(Math.Min(newVwap, (priceRow.LastPrice + fivePercentOfLast)), (priceRow.LastPrice - fivePercentOfLast)); didDataChange = true; } } // if something changeds update the data model if (didDataChange) { priceRow.RowVersion = ServerMarketData.RowVersion.Increment(); priceRow.AcceptChanges(); } } catch (Exception exception) { // Write the error and stack trace out to the debug listener MarkThree.EventLog.Error(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } finally { // Release the global tables. if (ServerMarketData.PriceLock.IsWriterLockHeld) { ServerMarketData.PriceLock.ReleaseWriterLock(); } if (ServerMarketData.SecurityLock.IsReaderLockHeld) { ServerMarketData.SecurityLock.ReleaseReaderLock(); } Debug.Assert(!ServerMarketData.IsLocked); } }
public static void OnUpdateSymbolDM(object wrapper, DMUpdateSymbolArgs args) { // StockInfo object holding the new symbol data for this update StockInfo stockInfo = args.stockInfo; try { // Lock the tables. Debug.Assert(!ServerMarketData.IsLocked); ServerMarketData.EquityLock.AcquireReaderLock(Timeout.Infinite); ServerMarketData.PriceLock.AcquireWriterLock(Timeout.Infinite); ServerMarketData.SecurityLock.AcquireReaderLock(Timeout.Infinite); // make sure the symbol is in the hash table - and that we added it to the data manager if (!symbolToSecurityIdTable.ContainsKey(stockInfo.Symbol)) { throw new Exception("Received update for unknown symbol: " + stockInfo.Symbol); } // get the security id of the symbol int securityID = (int)symbolToSecurityIdTable[stockInfo.Symbol]; // get the security row ServerMarketData.SecurityRow securityRow = ServerMarketData.Security.FindBySecurityId(securityID); if (securityRow == null) { throw new Exception("Warning: Could not find security row for symbol: " + stockInfo.Symbol); } //int equityId = Security.FindRequiredKey(configurationId, "equityId", externalEquityId); // Only Equities are priced by this simulator. All others are ignored. ServerMarketData.EquityRow equityRow = ServerMarketData.Equity.FindByEquityId(securityRow.SecurityId); if (equityRow != null) { // Find a price that matches the equity's default settlement. This is the price record that will be updated // with the simulated market conditions. ServerMarketData.PriceRow priceRow = ServerMarketData.Price.FindBySecurityId(securityRow.SecurityId); if (priceRow == null) { priceRow = ServerMarketData.Price.NewPriceRow(); //priceRow.RowVersion = ServerMarketData.RowVersion.Increment(); priceRow.SecurityId = equityRow.EquityId; priceRow.CurrencyId = equityRow.SettlementId; priceRow.LastPrice = 0.0M; priceRow.AskPrice = 0.0M; priceRow.BidPrice = 0.0M; priceRow.Volume = 0.0M; priceRow.ClosePrice = 0.0M; priceRow.VolumeWeightedAveragePrice = 0.0M; priceRow.HighPrice = 0.0M; priceRow.LowPrice = 0.0M; ServerMarketData.Price.AddPriceRow(priceRow); } // set the new values from the real time event into the price row priceRow.BidSize = stockInfo.BidSize; priceRow.BidPrice = Convert.ToDecimal(stockInfo.BidPrice); priceRow.AskPrice = Convert.ToDecimal(stockInfo.AskPrice); priceRow.AskSize = Convert.ToDecimal(stockInfo.AskSize); priceRow.LastPrice = Convert.ToDecimal(stockInfo.LastPrice); priceRow.LastSize = stockInfo.TradeVolume; priceRow.Volume = Convert.ToDecimal(stockInfo.TotalVolume); priceRow.VolumeWeightedAveragePrice = (Convert.ToDecimal(stockInfo.LastPrice) + Convert.ToDecimal(stockInfo.OpenPrice)) / 2; priceRow.OpenPrice = Convert.ToDecimal(stockInfo.OpenPrice); priceRow.LowPrice = Convert.ToDecimal(stockInfo.LowPrice); priceRow.HighPrice = Convert.ToDecimal(stockInfo.HighPrice); priceRow.ClosePrice = Convert.ToDecimal(stockInfo.PreviousPrice); // increment the RowVersion so the client notices!!! priceRow.RowVersion = ServerMarketData.RowVersion.Increment(); // commit the changes priceRow.AcceptChanges(); } } catch (Exception exception) { String msg = String.Format("{0}, {1}", exception.Message, exception.StackTrace); // Write the error and stack trace out to the debug listener //Debug.WriteLine(msg); MarkThree.EventLog.Warning(msg); } finally { // Release the global tables. if (ServerMarketData.EquityLock.IsReaderLockHeld) { ServerMarketData.EquityLock.ReleaseReaderLock(); } if (ServerMarketData.PriceLock.IsWriterLockHeld) { ServerMarketData.PriceLock.ReleaseWriterLock(); } if (ServerMarketData.SecurityLock.IsReaderLockHeld) { ServerMarketData.SecurityLock.ReleaseReaderLock(); } Debug.Assert(!ServerMarketData.IsLocked); } }