private void NotifyQuoteIsOk(InstrumentBidAskPair quote) { var message = $"Quotes for {quote.Instrument} started at {quote.Date}"; WriteMessage(quote, message, EventTypeEnum.QuoteStarted); _outdatedQuotes.Remove(quote.Instrument); }
private IEnumerable <Order> GetPendingOrdersToBeExecuted(InstrumentBidAskPair quote) { var pendingOrders = _ordersCache.Active.GetOrdersByInstrument(quote.Instrument); foreach (var order in pendingOrders) { var price = quote.GetPriceForOrderDirection(order.Direction); if (order.IsSuitablePriceForPendingOrder(price) && _validateOrderService.CheckIfPendingOrderExecutionPossible(order.AssetPairId, order.OrderType, ShouldOpenNewPosition(order))) { if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume)) { _ordersCache.Active.Remove(order); yield return(order); } else //let's validate one more time, considering orderbook depth { var me = _meRouter.GetMatchingEngineForExecution(order); var executionPriceInfo = me.GetBestPriceForOpen(order.AssetPairId, order.Volume); if (executionPriceInfo.price.HasValue && order.IsSuitablePriceForPendingOrder(executionPriceInfo.price.Value)) { _ordersCache.Active.Remove(order); yield return(order); } } } } }
public void Is_RelatedOrder_Validated_Correctly_Against_Base_MarketOrder_On_Create( OrderDirectionContract baseDirection, decimal?slPrice, decimal?tpPrice, OrderRejectReason?rejectReason) { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var orderRequest = new OrderPlaceRequest { AccountId = Accounts[0].Id, CorrelationId = Guid.NewGuid().ToString(), Direction = baseDirection, InstrumentId = instrument, Type = OrderTypeContract.Market, StopLoss = slPrice, TakeProfit = tpPrice, Volume = 1 }; if (!rejectReason.HasValue) { Assert.DoesNotThrowAsync(async() => await _validateOrderService.ValidateRequestAndCreateOrders(orderRequest)); } else { var ex1 = Assert.ThrowsAsync <ValidateOrderException>(() => _validateOrderService.ValidateRequestAndCreateOrders(orderRequest)); Assert.That(ex1.RejectReason == rejectReason); } }
public void Is_Volume_Ivalid(decimal volume, bool isValid) { const string instrument = "BTCUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = new Order { CreateDate = DateTime.UtcNow, Id = Guid.NewGuid().ToString("N"), AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, Instrument = instrument, Volume = volume, FillType = OrderFillType.FillOrKill }; if (isValid) { Assert.DoesNotThrow(() => _validateOrderService.Validate(order)); } else { var ex = Assert.Throws <ValidateOrderException>(() => _validateOrderService.Validate(order)); Assert.That(ex.RejectReason == OrderRejectReason.InvalidVolume); } }
public void Is_MarketOrder_Sell_StopLoss_Invalid() { const string instrument = "BTCCHF"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 963.633M, Ask = 964.228M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = new Order { CreateDate = DateTime.UtcNow, Id = Guid.NewGuid().ToString("N"), AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, TradingConditionId = MarginTradingTestsUtils.TradingConditionId, AccountAssetId = Accounts[0].BaseAssetId, Instrument = instrument, Volume = -10, StopLoss = 964.2553256564M, FillType = OrderFillType.FillOrKill }; var ex = Assert.Throws <ValidateOrderException>(() => _validateOrderService.Validate(order)); Assert.That(ex.RejectReason == OrderRejectReason.InvalidStoploss); StringAssert.Contains($"{quote.Bid}/{quote.Ask}", ex.Comment); StringAssert.Contains("more", ex.Message); }
public void Is_Order_ExpectedOpenPrice_Validated_Correctly(OrderDirectionContract direction, OrderTypeContract orderType, decimal?price, bool isValid) { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var request = new OrderPlaceRequest { AccountId = Accounts[0].Id, CorrelationId = Guid.NewGuid().ToString(), Direction = direction, InstrumentId = instrument, Type = orderType, Price = price, Volume = 1 }; if (isValid) { Assert.DoesNotThrowAsync(async() => await _validateOrderService.ValidateRequestAndCreateOrders(request)); } else { var ex = Assert.ThrowsAsync <ValidateOrderException>(() => _validateOrderService.ValidateRequestAndCreateOrders(request)); Assert.That(ex.RejectReason == OrderRejectReason.InvalidExpectedOpenPrice); StringAssert.Contains($"{quote.Bid}/{quote.Ask}", ex.Comment); } }
public void Is_Not_Enough_Balance() { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = new Order { CreateDate = DateTime.UtcNow, Id = Guid.NewGuid().ToString("N"), AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, TradingConditionId = MarginTradingTestsUtils.TradingConditionId, AccountAssetId = Accounts[0].BaseAssetId, Instrument = instrument, Volume = 150000, FillType = OrderFillType.FillOrKill, }; var ex = Assert.Throws <ValidateOrderException>(() => _validateOrderService.Validate(order)); Assert.That(ex.RejectReason == OrderRejectReason.NotEnoughBalance); }
private void UpdateBestPrice() { var newBestPrice = new InstrumentBidAskPair { Instrument = Instrument, Date = DateTime.UtcNow }; if (Sell.Any()) { var fl = Sell.First(); newBestPrice.Ask = fl.Key; newBestPrice.AskFirstLevelVolume = fl.Value.Sum(o => Math.Abs(o.Volume)); } else { newBestPrice.Ask = BestPrice?.Ask ?? 0; } if (Buy.Any()) { var fl = Buy.First(); newBestPrice.Bid = fl.Key; newBestPrice.BidFirstLevelVolume = fl.Value.Sum(o => Math.Abs(o.Volume)); } else { newBestPrice.Bid = BestPrice?.Bid ?? 0; } if (newBestPrice.Ask > 0 && newBestPrice.Bid > 0) { BestPrice = newBestPrice; } }
public void Is_Sell_Order_ExpectedOpenPrice_Invalid() { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = new Order { CreateDate = DateTime.UtcNow, Id = Guid.NewGuid().ToString("N"), AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, TradingConditionId = MarginTradingTestsUtils.TradingConditionId, AccountAssetId = Accounts[0].BaseAssetId, Instrument = instrument, Volume = -10, ExpectedOpenPrice = 1.54532567434M, FillType = OrderFillType.FillOrKill }; var ex = Assert.Throws <ValidateOrderException>(() => _validateOrderService.Validate(order)); Assert.That(ex.RejectReason == OrderRejectReason.InvalidExpectedOpenPrice); StringAssert.Contains($"{quote.Bid}/{quote.Ask}", ex.Comment); }
private void ProcessPositions(InstrumentBidAskPair quote) { var stopoutAccounts = UpdateClosePriceAndDetectStopout(quote); foreach (var account in stopoutAccounts) { CommitStopOut(account, quote); } }
public OrderBook(string instrument) { Instrument = instrument; Buy = new SortedDictionary <decimal, List <LimitOrder> >(new ReverseComparer <decimal>(Comparer <decimal> .Default)); Sell = new SortedDictionary <decimal, List <LimitOrder> >(); BestPrice = new InstrumentBidAskPair { Instrument = instrument, Date = DateTime.UtcNow }; }
public static BidAskClientContract ToClientContract(this InstrumentBidAskPair src) { return(new BidAskClientContract { Id = src.Instrument, Date = src.Date, Bid = src.Bid, Ask = src.Ask }); }
// private void ProcessPendingOrdersMarginRecalc(string instrument) // { // var pendingOrders = _ordersCache.GetPendingForMarginRecalc(instrument); // // foreach (var pendingOrder in pendingOrders) // { // pendingOrder.UpdatePendingOrderMargin(); // } // } #endregion #region Positions private void UpdatePositionsFxRates(InstrumentBidAskPair quote) { foreach (var position in _ordersCache.GetPositionsByFxAssetPairId(quote.Instrument)) { var fxPrice = _cfdCalculatorService.GetPrice(quote, position.FxToAssetPairDirection, position.Volume * (position.ClosePrice - position.OpenPrice) > 0); position.UpdateCloseFxPrice(fxPrice); } }
private void WriteMessage(InstrumentBidAskPair quote, string message, EventTypeEnum eventType) { _log.WriteInfoAsync(nameof(QuotesMonitor), quote.ToJson(), message); var slackChannelType = _alertSeverityLevelService.GetSlackChannelType(eventType); if (!string.IsNullOrWhiteSpace(slackChannelType)) { _slackNotificationsSender.SendRawAsync(slackChannelType, nameof(QuotesMonitor), message); } }
public static InstrumentBidAskPairContract ToBackendContract(this InstrumentBidAskPair src) { return(new InstrumentBidAskPairContract { Id = src.Instrument, Date = src.Date, Bid = src.Bid, Ask = src.Ask }); }
public static BestPriceContract ConvertToContract(this InstrumentBidAskPair arg) { return(new BestPriceContract { Ask = arg.Ask, Bid = arg.Bid, Id = arg.Instrument, Timestamp = arg.Date, }); }
public static BidAskPairRabbitMqContract ToRabbitMqContract(this InstrumentBidAskPair pair) { return(new BidAskPairRabbitMqContract { Instrument = pair.Instrument, Ask = pair.Ask, Bid = pair.Bid, Date = pair.Date }); }
private List <MarginTradingAccount> UpdateClosePriceAndDetectStopout(InstrumentBidAskPair quote) { var positionsByAccounts = _ordersCache.Positions.GetPositionsByInstrument(quote.Instrument) .GroupBy(x => x.AccountId).ToDictionary(x => x.Key, x => x.ToArray()); var accountsWithStopout = new List <MarginTradingAccount>(); foreach (var accountPositions in positionsByAccounts) { var account = _accountsCacheService.Get(accountPositions.Key); var oldAccountLevel = account.GetAccountLevel(); foreach (var position in accountPositions.Value) { var closeOrderDirection = position.Volume.GetClosePositionOrderDirection(); var closePrice = quote.GetPriceForOrderDirection(closeOrderDirection); if (quote.GetVolumeForOrderDirection(closeOrderDirection) < Math.Abs(position.Volume)) { var defaultMatchingEngine = _meRouter.GetMatchingEngineForClose(position.OpenMatchingEngineId); var orderbookPrice = defaultMatchingEngine.GetPriceForClose(position.AssetPairId, position.Volume, position.ExternalProviderId); if (orderbookPrice.HasValue) { closePrice = orderbookPrice.Value; } } if (closePrice != 0) { position.UpdateClosePriceWithoutAccountUpdate(closePrice); UpdateTrailingStops(position); } } account.CacheNeedsToBeUpdated(); var newAccountLevel = account.GetAccountLevel(); if (newAccountLevel == AccountLevel.StopOut) { accountsWithStopout.Add(account); } if (oldAccountLevel != newAccountLevel) { _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account, newAccountLevel)); } } return(accountsWithStopout); }
public static BidAskPairRabbitMqContract ToRabbitMqContract(this InstrumentBidAskPair pair, bool isEod) { return(new BidAskPairRabbitMqContract { Instrument = pair.Instrument, Ask = pair.Ask, Bid = pair.Bid, Date = pair.Date, IsEod = isEod ? true : (bool?)null, }); }
public decimal GetPrice(InstrumentBidAskPair quote, FxToAssetPairDirection direction, bool metricIsPositive = true) { return(metricIsPositive ? direction == FxToAssetPairDirection.Straight ? quote.Ask : 1 / quote.Bid : direction == FxToAssetPairDirection.Straight ? quote.Bid : 1 / quote.Ask); }
private void NotifyQuoteIsOutdated(InstrumentBidAskPair quote) { var message = $"Quotes for {quote.Instrument} stopped at {quote.Date}!"; WriteMessage(quote, message, EventTypeEnum.QuoteStopped); var info = new OutdatedQuoteInfo { LastQuoteRecieved = quote.Date, LastNotificationSend = _dateService.Now() }; _outdatedQuotes[quote.Instrument] = info; }
private void UpdateBestPrice() { var newBestPrice = new InstrumentBidAskPair { Instrument = Instrument, Ask = Sell.Any() ? Sell.First().Key : BestPrice?.Ask ?? 0, Bid = Buy.Any() ? Buy.First().Key : BestPrice?.Bid ?? 0, Date = DateTime.UtcNow }; if (newBestPrice.Ask > 0 && newBestPrice.Bid > 0) { BestPrice = newBestPrice; } }
public void Is_Not_Enough_Balance() { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = TestObjectsFactory.CreateNewOrder(OrderType.Market, instrument, Accounts[0], MarginTradingTestsUtils.TradingConditionId, 150000); var ex = Assert.Throws <ValidateOrderException>(() => _validateOrderService.MakePreTradeValidation(order, true, _me)); Assert.That(ex.RejectReason == OrderRejectReason.NotEnoughBalance); }
private void SetupAssetPair(string id, bool isDiscontinued = false, bool isFrozen = false, bool isSuspended = false) { var pair = _assetPairsCache.GetAssetPairById(id); _assetPairsCache.AddOrUpdate( new AssetPair(pair.Id, pair.Name, pair.BaseAssetId, pair.QuoteAssetId, pair.Accuracy, pair.LegalEntity, pair.BaseAssetId, pair.MatchingEngineMode, pair.StpMultiplierMarkupAsk, pair.StpMultiplierMarkupBid, isSuspended, isFrozen, isDiscontinued)); var quote = new InstrumentBidAskPair { Instrument = id, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); }
public void Is_Enough_Balance_When_Additional_Margin_Exists() { const string instrument = "EURUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var order = TestObjectsFactory.CreateNewOrder(OrderType.Market, instrument, Accounts[0], MarginTradingTestsUtils.TradingConditionId, 150000); //account margin = 1000, margin requirement for order = 2355 => additional margin should be > 1355 Assert.DoesNotThrow(() => _validateOrderService.MakePreTradeValidation(order, true, _me, 1356)); }
public void SetOrderbook(ExternalOrderBook orderbook) { if (!ValidateOrderbook(orderbook)) { return; } var bba = new InstrumentBidAskPair { Bid = 0, Ask = decimal.MaxValue, Date = _dateService.Now(), Instrument = orderbook.AssetPairId }; Dictionary <string, ExternalOrderBook> UpdateOrderbooksDictionary(string assetPairId, Dictionary <string, ExternalOrderBook> dict) { dict[orderbook.ExchangeName] = orderbook; foreach (var pair in dict.Values.RequiredNotNullOrEmptyCollection(nameof(dict))) { // guaranteed to be sorted best first var bestBid = pair.Bids.First().Price; var bestAsk = pair.Asks.First().Price; if (bestBid > bba.Bid) { bba.Bid = bestBid; } if (bestAsk < bba.Ask) { bba.Ask = bestAsk; } } return(dict); } _orderbooks.AddOrUpdate(orderbook.AssetPairId, k => UpdateOrderbooksDictionary(k, new Dictionary <string, ExternalOrderBook>()), UpdateOrderbooksDictionary); _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba)); }
public bool TryGetQuoteById(string instrument, out InstrumentBidAskPair result) { _lockSlim.EnterReadLock(); try { if (!_cache.TryGetValue(instrument, out var quote)) { result = null; return(false); } result = quote; return(true); } finally { _lockSlim.ExitReadLock(); } }
public void Is_Volume_Ivalid(decimal volume, bool isValid) { const string instrument = "BTCUSD"; var quote = new InstrumentBidAskPair { Instrument = instrument, Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var request = new OrderPlaceRequest { AccountId = Accounts[0].Id, CorrelationId = Guid.NewGuid().ToString(), Direction = OrderDirectionContract.Buy, InstrumentId = instrument, Type = OrderTypeContract.Market, Volume = volume }; if (isValid) { Assert.DoesNotThrow( () => { var order = _validateOrderService.ValidateRequestAndCreateOrders(request).Result.order; _validateOrderService.MakePreTradeValidation(order, true, _me); }); } else { var ex = Assert.ThrowsAsync <ValidateOrderException>( async() => { var order = (await _validateOrderService.ValidateRequestAndCreateOrders(request)).order; _validateOrderService.MakePreTradeValidation(order, true, _me); }); Assert.That(ex.RejectReason == (volume == 0 ? OrderRejectReason.InvalidVolume : OrderRejectReason.MaxOrderSizeLimit)); } }
private void ProcessOrdersWaitingForExecution(InstrumentBidAskPair quote) { //TODO: MTC-155 //ProcessPendingOrdersMarginRecalc(instrument); var orders = GetPendingOrdersToBeExecuted(quote).GetSortedForExecution(); if (!orders.Any()) { return; } foreach (var order in orders) { _threadSwitcher.SwitchThread(async() => { await PlaceOrderByMarketPrice(order); }); } }
public void Is_ValidityDate_Valid_ForNotMarket() { var quote = new InstrumentBidAskPair { Instrument = "BTCUSD", Bid = 1.55M, Ask = 1.57M }; _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(quote)); var request = new OrderPlaceRequest { AccountId = Accounts[0].Id, Direction = OrderDirectionContract.Buy, InstrumentId = "BTCUSD", Type = OrderTypeContract.Limit, Price = 1, Validity = DateTime.UtcNow.Date, Volume = 10 }; Assert.DoesNotThrowAsync(async() => await _validateOrderService.ValidateRequestAndCreateOrders(request)); }