/// <summary> /// Downloads a list of TradeBars at the requested resolution /// </summary> /// <param name="symbol">The symbol</param> /// <param name="startTimeUtc">The starting time (UTC)</param> /// <param name="endTimeUtc">The ending time (UTC)</param> /// <param name="resolution">The requested resolution</param> /// <param name="requestedTimeZone">The requested timezone for the data</param> /// <returns>The list of bars</returns> public override IEnumerable <TradeBar> DownloadTradeBars(Symbol symbol, DateTime startTimeUtc, DateTime endTimeUtc, Resolution resolution, DateTimeZone requestedTimeZone) { var oandaSymbol = SymbolMapper.GetBrokerageSymbol(symbol); var startUtc = startTimeUtc.ToString("yyyy-MM-ddTHH:mm:ssZ"); var candles = GetCandles(oandaSymbol, startUtc, OandaBrokerage.MaxBarsPerRequest, resolution, ECandleFormat.midpoint); foreach (var candle in candles) { var time = OandaBrokerage.GetDateTimeFromString(candle.time); if (time > endTimeUtc) { break; } yield return(new TradeBar( time.ConvertFromUtc(requestedTimeZone), symbol, Convert.ToDecimal(candle.openMid), Convert.ToDecimal(candle.highMid), Convert.ToDecimal(candle.lowMid), Convert.ToDecimal(candle.closeMid), 0, resolution.ToTimeSpan())); } }
/// <summary> /// Updates the order with the same id /// </summary> /// <param name="order">The new order information</param> /// <returns>True if the request was made for the order to be updated, false otherwise</returns> public override bool UpdateOrder(Order order) { Log.Trace("OandaBrokerage.UpdateOrder(): " + order); if (!order.BrokerId.Any()) { // we need the brokerage order id in order to perform an update Log.Trace("OandaBrokerage.UpdateOrder(): Unable to update order without BrokerId."); return(false); } var requestParams = new Dictionary <string, string> { { "instrument", SymbolMapper.GetBrokerageSymbol(order.Symbol) }, { "units", order.AbsoluteQuantity.ConvertInvariant <int>().ToStringInvariant() }, }; // we need the brokerage order id in order to perform an update PopulateOrderRequestParameters(order, requestParams); if (UpdateOrder(Parse.Long(order.BrokerId.First()), requestParams)) { OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, OrderFee.Zero) { Status = OrderStatus.UpdateSubmitted }); } return(true); }
/// <summary> /// Downloads a list of TradeBars at the requested resolution /// </summary> /// <param name="symbol">The symbol</param> /// <param name="startTimeUtc">The starting time (UTC)</param> /// <param name="endTimeUtc">The ending time (UTC)</param> /// <param name="resolution">The requested resolution</param> /// <param name="requestedTimeZone">The requested timezone for the data</param> /// <returns>The list of bars</returns> public override IEnumerable <TradeBar> DownloadTradeBars(Symbol symbol, DateTime startTimeUtc, DateTime endTimeUtc, Resolution resolution, DateTimeZone requestedTimeZone) { var oandaSymbol = SymbolMapper.GetBrokerageSymbol(symbol); var startUtc = startTimeUtc.ToStringInvariant("yyyy-MM-ddTHH:mm:ssZ"); var endUtc = endTimeUtc.ToStringInvariant("yyyy-MM-ddTHH:mm:ssZ"); // Oanda only has 5-second bars, we return these for Resolution.Second var period = resolution == Resolution.Second ? TimeSpan.FromSeconds(5) : resolution.ToTimeSpan(); var response = _apiRest.GetInstrumentCandles(Authorization, oandaSymbol, null, "M", ToGranularity(resolution).ToString(), null, startUtc, endUtc); foreach (var candle in response.Candles) { var time = GetTickDateTimeFromString(candle.Time); if (time > endTimeUtc) { break; } yield return(new TradeBar( time.ConvertFromUtc(requestedTimeZone), symbol, candle.Bid.O.ToDecimal(), candle.Bid.H.ToDecimal(), candle.Bid.L.ToDecimal(), candle.Bid.C.ToDecimal(), 0, period)); } }
/// <summary> /// Converts an Oanda position into a LEAN holding. /// </summary> private Holding ConvertHolding(Position position) { var securityType = SymbolMapper.GetBrokerageSecurityType(position.Instrument); var symbol = SymbolMapper.GetLeanSymbol(position.Instrument, securityType, Market.Oanda); var longUnits = position._Long.Units.ConvertInvariant <int>(); var shortUnits = position._Short.Units.ConvertInvariant <int>(); decimal averagePrice = 0; var quantity = 0; if (longUnits > 0) { averagePrice = position._Long.AveragePrice.ToDecimal(); quantity = longUnits; } else if (shortUnits < 0) { averagePrice = position._Short.AveragePrice.ToDecimal(); quantity = shortUnits; } return(new Holding { Symbol = symbol, Type = securityType, AveragePrice = averagePrice, CurrencySymbol = "$", Quantity = quantity }); }
/// <summary> /// Event handler for streaming ticks /// </summary> /// <param name="data">The data object containing the received tick</param> private void OnDataReceived(RateStreamResponse data) { if (data.IsHeartbeat()) { PricingConnectionHandler.KeepAlive(DateTime.UtcNow); return; } if (data.tick == null) { return; } var securityType = SymbolMapper.GetBrokerageSecurityType(data.tick.instrument); var symbol = SymbolMapper.GetLeanSymbol(data.tick.instrument, securityType, Market.Oanda); var time = OandaBrokerage.GetDateTimeFromString(data.tick.time); // live ticks timestamps must be in exchange time zone DateTimeZone exchangeTimeZone; if (!_symbolExchangeTimeZones.TryGetValue(symbol, out exchangeTimeZone)) { exchangeTimeZone = MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, symbol, securityType).TimeZone; _symbolExchangeTimeZones.Add(symbol, exchangeTimeZone); } time = time.ConvertFromUtc(exchangeTimeZone); var bidPrice = Convert.ToDecimal(data.tick.bid); var askPrice = Convert.ToDecimal(data.tick.ask); var tick = new Tick(time, symbol, bidPrice, askPrice); EmitTick(tick); }
/// <summary> /// Event handler for streaming ticks /// </summary> /// <param name="json">The data object containing the received tick</param> private void OnPricingDataReceived(string json) { var obj = (JObject)JsonConvert.DeserializeObject(json); var type = obj["type"].ToString(); switch (type) { case "HEARTBEAT": PricingConnectionHandler.KeepAlive(DateTime.UtcNow); break; case "PRICE": var data = obj.ToObject <Price>(); var securityType = SymbolMapper.GetBrokerageSecurityType(data.Instrument); var symbol = SymbolMapper.GetLeanSymbol(data.Instrument, securityType, Market.Oanda); var time = GetTickDateTimeFromString(data.Time); // live ticks timestamps must be in exchange time zone DateTimeZone exchangeTimeZone; if (!_symbolExchangeTimeZones.TryGetValue(symbol, out exchangeTimeZone)) { exchangeTimeZone = MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, symbol, securityType).TimeZone; _symbolExchangeTimeZones.Add(symbol, exchangeTimeZone); } time = time.ConvertFromUtc(exchangeTimeZone); var bidPrice = data.Bids.Last().Price.ConvertInvariant <decimal>(); var askPrice = data.Asks.Last().Price.ConvertInvariant <decimal>(); var tick = new Tick(time, symbol, bidPrice, askPrice); EmitTick(tick); break; } }
/// <summary> /// Converts an Oanda order into a LEAN order. /// </summary> private Order ConvertOrder(JToken order) { var type = order["type"].ToString(); Order qcOrder; var instrument = order["instrument"].ToString(); var id = order["id"].ToString(); var units = order["units"].ConvertInvariant <decimal>(); var createTime = order["createTime"].ToString(); var securityType = SymbolMapper.GetBrokerageSecurityType(instrument); var symbol = SymbolMapper.GetLeanSymbol(instrument, securityType, Market.Oanda); var time = GetTickDateTimeFromString(createTime); var quantity = units; switch (type) { case "MARKET_IF_TOUCHED": var stopOrder = order.ToObject <MarketIfTouchedOrder>(); qcOrder = new StopMarketOrder { StopPrice = stopOrder.Price.ToDecimal() }; break; case "LIMIT": var limitOrder = order.ToObject <OandaLimitOrder>(); qcOrder = new LimitOrder(symbol, quantity, limitOrder.Price.ToDecimal(), time); break; case "STOP": var stopLimitOrder = order.ToObject <StopOrder>(); var price = stopLimitOrder.Price.ConvertInvariant <decimal>(); var limitPrice = stopLimitOrder.PriceBound.ConvertInvariant <decimal>(); qcOrder = new StopLimitOrder(symbol, quantity, price, limitPrice, time); break; case "MARKET": qcOrder = new MarketOrder(); break; default: throw new NotSupportedException( "An existing " + type + " working order was found and is currently unsupported. Please manually cancel the order before restarting the algorithm."); } qcOrder.Status = OrderStatus.None; qcOrder.BrokerId.Add(id); var gtdTime = order["gtdTime"]; if (gtdTime != null) { var expiry = GetTickDateTimeFromString(gtdTime.ToString()); qcOrder.Properties.TimeInForce = TimeInForce.GoodTilDate(expiry); } return(qcOrder); }
/// <summary> /// Generates an Oanda order request /// </summary> /// <param name="order">The LEAN order</param> /// <returns>The request in JSON format</returns> private string GenerateOrderRequest(Order order) { var instrument = SymbolMapper.GetBrokerageSymbol(order.Symbol); string request; switch (order.Type) { case OrderType.Market: var marketOrderRequest = new MarketOrderRequest { Type = MarketOrderRequest.TypeEnum.MARKET, Instrument = instrument, Units = order.Quantity.ToStringInvariant() }; request = JsonConvert.SerializeObject(new { order = marketOrderRequest }); break; case OrderType.Limit: var limitOrderRequest = new LimitOrderRequest { Type = LimitOrderRequest.TypeEnum.LIMIT, Instrument = instrument, Units = order.Quantity.ToStringInvariant(), Price = ((LimitOrder)order).LimitPrice.ToString(CultureInfo.InvariantCulture) }; request = JsonConvert.SerializeObject(new { order = limitOrderRequest }); break; case OrderType.StopMarket: var marketIfTouchedOrderRequest = new MarketIfTouchedOrderRequest { Type = MarketIfTouchedOrderRequest.TypeEnum.MARKETIFTOUCHED, Instrument = instrument, Units = order.Quantity.ToStringInvariant(), Price = ((StopMarketOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture) }; request = JsonConvert.SerializeObject(new { order = marketIfTouchedOrderRequest }); break; case OrderType.StopLimit: var stopOrderRequest = new StopOrderRequest { Type = StopOrderRequest.TypeEnum.STOP, Instrument = instrument, Units = order.Quantity.ToStringInvariant(), Price = ((StopLimitOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture), PriceBound = ((StopLimitOrder)order).LimitPrice.ToString(CultureInfo.InvariantCulture) }; request = JsonConvert.SerializeObject(new { order = stopOrderRequest }); break; default: throw new NotSupportedException("The order type " + order.Type + " is not supported."); } return(request); }
/// <summary> /// Converts the Oanda position into a QuantConnect holding. /// </summary> /// <param name="position">The position.</param> /// <returns></returns> private Holding ConvertHolding(Position position) { var securityType = SymbolMapper.GetBrokerageSecurityType(position.instrument); return(new Holding { Symbol = SymbolMapper.GetLeanSymbol(position.instrument, securityType, Market.Oanda), Type = securityType, AveragePrice = (decimal)position.avgPrice, CurrencySymbol = "$", Quantity = position.side == "sell" ? -position.units : position.units }); }
/// <summary> /// Downloads a list of QuoteBars at the requested resolution /// </summary> /// <param name="symbol">The symbol</param> /// <param name="startTimeUtc">The starting time (UTC)</param> /// <param name="endTimeUtc">The ending time (UTC)</param> /// <param name="resolution">The requested resolution</param> /// <param name="requestedTimeZone">The requested timezone for the data</param> /// <returns>The list of bars</returns> public override IEnumerable <QuoteBar> DownloadQuoteBars(Symbol symbol, DateTime startTimeUtc, DateTime endTimeUtc, Resolution resolution, DateTimeZone requestedTimeZone) { var oandaSymbol = SymbolMapper.GetBrokerageSymbol(symbol); var startUtc = startTimeUtc.ToString("yyyy-MM-ddTHH:mm:ssZ"); // Oanda only has 5-second bars, we return these for Resolution.Second var period = resolution == Resolution.Second ? TimeSpan.FromSeconds(5) : resolution.ToTimeSpan(); var candles = GetCandles(oandaSymbol, startUtc, OandaBrokerage.MaxBarsPerRequest, resolution, ECandleFormat.bidask); foreach (var candle in candles) { var time = OandaBrokerage.GetDateTimeFromString(candle.time); if (time > endTimeUtc) { break; } yield return(new QuoteBar( time.ConvertFromUtc(requestedTimeZone), symbol, new Bar( Convert.ToDecimal(candle.openBid), Convert.ToDecimal(candle.highBid), Convert.ToDecimal(candle.lowBid), Convert.ToDecimal(candle.closeBid) ), 0, new Bar( Convert.ToDecimal(candle.openAsk), Convert.ToDecimal(candle.highAsk), Convert.ToDecimal(candle.lowAsk), Convert.ToDecimal(candle.closeAsk) ), 0, period)); } }
/// <summary> /// Updates the order with the same id /// </summary> /// <param name="order">The new order information</param> /// <returns>True if the request was made for the order to be updated, false otherwise</returns> public override bool UpdateOrder(Order order) { Log.Trace("OandaBrokerage.UpdateOrder(): " + order); if (!order.BrokerId.Any()) { // we need the brokerage order id in order to perform an update Log.Trace("OandaBrokerage.UpdateOrder(): Unable to update order without BrokerId."); return(false); } var requestParams = new Dictionary <string, string> { { "instrument", SymbolMapper.GetBrokerageSymbol(order.Symbol) }, { "units", Convert.ToInt32(order.AbsoluteQuantity).ToString() }, }; // we need the brokerage order id in order to perform an update PopulateOrderRequestParameters(order, requestParams); UpdateOrder(long.Parse(order.BrokerId.First()), requestParams); return(true); }
public EtradeParser() { m_SymbolMapper = new SymbolMapper(); }
/// <summary> /// Places a new order and assigns a new broker ID to the order /// </summary> /// <param name="order">The order to be placed</param> /// <returns>True if the request for a new order has been placed, false otherwise</returns> public override bool PlaceOrder(Order order) { var requestParams = new Dictionary <string, string> { { "instrument", SymbolMapper.GetBrokerageSymbol(order.Symbol) }, { "units", Convert.ToInt32(order.AbsoluteQuantity).ToString() } }; PopulateOrderRequestParameters(order, requestParams); var postOrderResponse = PostOrderAsync(requestParams); if (postOrderResponse == null) { return(false); } // if market order, find fill quantity and price var marketOrderFillPrice = 0m; if (order.Type == OrderType.Market) { marketOrderFillPrice = Convert.ToDecimal(postOrderResponse.price); } var marketOrderFillQuantity = 0; if (postOrderResponse.tradeOpened != null && postOrderResponse.tradeOpened.id > 0) { if (order.Type == OrderType.Market) { marketOrderFillQuantity = postOrderResponse.tradeOpened.units; } else { order.BrokerId.Add(postOrderResponse.tradeOpened.id.ToString()); } } if (postOrderResponse.tradeReduced != null && postOrderResponse.tradeReduced.id > 0) { if (order.Type == OrderType.Market) { marketOrderFillQuantity = postOrderResponse.tradeReduced.units; } else { order.BrokerId.Add(postOrderResponse.tradeReduced.id.ToString()); } } if (postOrderResponse.orderOpened != null && postOrderResponse.orderOpened.id > 0) { if (order.Type != OrderType.Market) { order.BrokerId.Add(postOrderResponse.orderOpened.id.ToString()); } } if (postOrderResponse.tradesClosed != null && postOrderResponse.tradesClosed.Count > 0) { marketOrderFillQuantity += postOrderResponse.tradesClosed .Where(trade => order.Type == OrderType.Market) .Sum(trade => trade.units); } // send Submitted order event const int orderFee = 0; order.PriceCurrency = SecurityProvider.GetSecurity(order.Symbol).SymbolProperties.QuoteCurrency; OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = OrderStatus.Submitted }); if (order.Type == OrderType.Market) { // if market order, also send Filled order event OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = OrderStatus.Filled, FillPrice = marketOrderFillPrice, FillQuantity = marketOrderFillQuantity * Math.Sign(order.Quantity) }); } return(true); }
/// <summary> /// Places a new order and assigns a new broker ID to the order /// </summary> /// <param name="order">The order to be placed</param> /// <returns>True if the request for a new order has been placed, false otherwise</returns> public override bool PlaceOrder(Order order) { var requestParams = new Dictionary <string, string> { { "instrument", SymbolMapper.GetBrokerageSymbol(order.Symbol) }, { "units", Convert.ToInt32(order.AbsoluteQuantity).ToString() } }; var orderFee = OrderFee.Zero; var marketOrderFillQuantity = 0; var marketOrderRemainingQuantity = 0; decimal marketOrderFillPrice; var marketOrderStatus = OrderStatus.Filled; order.PriceCurrency = SecurityProvider.GetSecurity(order.Symbol).SymbolProperties.QuoteCurrency; PopulateOrderRequestParameters(order, requestParams); lock (Locker) { var postOrderResponse = PostOrderAsync(requestParams); if (postOrderResponse == null) { return(false); } // Market orders are special, due to the callback not being triggered always, if the order was filled, // find fill quantity and price and inform the user if (postOrderResponse.tradeOpened != null && postOrderResponse.tradeOpened.id > 0) { if (order.Type == OrderType.Market) { marketOrderFillQuantity = postOrderResponse.tradeOpened.units; } else { order.BrokerId.Add(postOrderResponse.tradeOpened.id.ToString()); } } if (postOrderResponse.tradeReduced != null && postOrderResponse.tradeReduced.id > 0) { if (order.Type == OrderType.Market) { marketOrderFillQuantity = postOrderResponse.tradeReduced.units; } else { order.BrokerId.Add(postOrderResponse.tradeReduced.id.ToString()); } } if (postOrderResponse.orderOpened != null && postOrderResponse.orderOpened.id > 0) { if (order.Type != OrderType.Market) { order.BrokerId.Add(postOrderResponse.orderOpened.id.ToString()); } } if (postOrderResponse.tradesClosed != null && postOrderResponse.tradesClosed.Count > 0) { marketOrderFillQuantity += postOrderResponse.tradesClosed .Where(trade => order.Type == OrderType.Market) .Sum(trade => trade.units); } marketOrderFillPrice = Convert.ToDecimal(postOrderResponse.price); marketOrderRemainingQuantity = Convert.ToInt32(order.AbsoluteQuantity - Math.Abs(marketOrderFillQuantity)); if (marketOrderRemainingQuantity > 0) { marketOrderStatus = OrderStatus.PartiallyFilled; // The order was not fully filled lets save it so the callback can inform the user PendingFilledMarketOrders[order.Id] = marketOrderStatus; } } OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = OrderStatus.Submitted }); // If 'marketOrderRemainingQuantity < order.AbsoluteQuantity' is false it means the order was not even PartiallyFilled, wait for callback if (order.Type == OrderType.Market && marketOrderRemainingQuantity < order.AbsoluteQuantity) { OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = marketOrderStatus, FillPrice = marketOrderFillPrice, FillQuantity = marketOrderFillQuantity * Math.Sign(order.Quantity) }); } return(true); }
/// <summary> /// Converts an Oanda order into a LEAN order. /// </summary> private Order ConvertOrder(JToken order) { var type = order["type"].ToString(); Order qcOrder; switch (type) { case "MARKET_IF_TOUCHED": var stopOrder = order.ToObject <MarketIfTouchedOrder>(); qcOrder = new StopMarketOrder { StopPrice = stopOrder.Price.ToDecimal() }; break; case "LIMIT": var limitOrder = order.ToObject <OandaLimitOrder>(); qcOrder = new LimitOrder { LimitPrice = limitOrder.Price.ToDecimal() }; break; case "STOP": var stopLimitOrder = order.ToObject <StopOrder>(); qcOrder = new StopLimitOrder { Price = Convert.ToDecimal(stopLimitOrder.Price), LimitPrice = Convert.ToDecimal(stopLimitOrder.PriceBound) }; break; case "MARKET": qcOrder = new MarketOrder(); break; default: throw new NotSupportedException( "An existing " + type + " working order was found and is currently unsupported. Please manually cancel the order before restarting the algorithm."); } var instrument = order["instrument"].ToString(); var id = order["id"].ToString(); var units = Convert.ToInt32(order["units"]); var createTime = order["createTime"].ToString(); var securityType = SymbolMapper.GetBrokerageSecurityType(instrument); qcOrder.Symbol = SymbolMapper.GetLeanSymbol(instrument, securityType, Market.Oanda); qcOrder.Time = GetTickDateTimeFromString(createTime); qcOrder.Quantity = units; qcOrder.Status = OrderStatus.None; qcOrder.BrokerId.Add(id); var orderByBrokerageId = OrderProvider.GetOrderByBrokerageId(id); if (orderByBrokerageId != null) { qcOrder.Id = orderByBrokerageId.Id; } var gtdTime = order["gtdTime"]; if (gtdTime != null) { qcOrder.Duration = OrderDuration.Custom; qcOrder.DurationValue = GetTickDateTimeFromString(gtdTime.ToString()); } return(qcOrder); }
/// <summary> /// Converts the specified Oanda order into a qc order. /// The 'task' will have a value if we needed to issue a rest call for the stop price, otherwise it will be null /// </summary> private Order ConvertOrder(RestV1.DataType.Order order) { Order qcOrder; switch (order.type) { case "limit": qcOrder = new LimitOrder(); if (order.side == "buy") { ((LimitOrder)qcOrder).LimitPrice = Convert.ToDecimal(order.lowerBound); } if (order.side == "sell") { ((LimitOrder)qcOrder).LimitPrice = Convert.ToDecimal(order.upperBound); } break; case "stop": qcOrder = new StopLimitOrder(); if (order.side == "buy") { ((StopLimitOrder)qcOrder).LimitPrice = Convert.ToDecimal(order.lowerBound); } if (order.side == "sell") { ((StopLimitOrder)qcOrder).LimitPrice = Convert.ToDecimal(order.upperBound); } break; case "marketIfTouched": //when market reaches the price sell at market. qcOrder = new StopMarketOrder { Price = Convert.ToDecimal(order.price), StopPrice = Convert.ToDecimal(order.price) }; break; case "market": qcOrder = new MarketOrder(); break; default: throw new NotSupportedException("The Oanda order type " + order.type + " is not supported."); } var securityType = SymbolMapper.GetBrokerageSecurityType(order.instrument); qcOrder.Symbol = SymbolMapper.GetLeanSymbol(order.instrument, securityType, Market.Oanda); qcOrder.Quantity = ConvertQuantity(order); qcOrder.Status = OrderStatus.None; qcOrder.BrokerId.Add(order.id.ToString()); var orderByBrokerageId = OrderProvider.GetOrderByBrokerageId(order.id); if (orderByBrokerageId != null) { qcOrder.Id = orderByBrokerageId.Id; } var expiry = XmlConvert.ToDateTime(order.expiry, XmlDateTimeSerializationMode.Utc); qcOrder.Properties.TimeInForce = TimeInForce.GoodTilDate(expiry); qcOrder.Time = XmlConvert.ToDateTime(order.time, XmlDateTimeSerializationMode.Utc); return(qcOrder); }
public bool TryGenerate(IPContext context, string input, out IFilterParticle <TEntity> particle) { input = input.Trim(); // Attempt to capture field name from filter string. var nameMatch = Regex.Match(input, "^([A-Za-z0-9_]*)"); if (!nameMatch.Success || nameMatch.Length == 0) { particle = null; return(false); } // Validate the property by name if (!TryGetPropertyByAlias(nameMatch.Value, out PropertyInfo property)) { particle = null; return(false); } // Remove the name out of the filter string. var remaining = input.Substring(nameMatch.Length).TrimStart(); // Attempt to capture operator from filter string. // Keys are ordered by descending length to always match the longest one possible first. (eg, "!=" instead "=") var operatorKey = SymbolMapper.Symbols.FirstOrDefault(o => remaining.StartsWith(o)); if (operatorKey is null) { particle = null; return(false); } var op = SymbolMapper.Map(operatorKey); // Remove the operator out of the remaining filter string. var value = remaining.Substring(operatorKey.Length).Trim(); // If encased in double quotes, we remove them. if (value.StartsWith("\"") && value.EndsWith("\"")) { value = value.Substring(1, value.Length - 2); } try { // Attempt to convert/cast the value to the type of the property. var converter = TypeDescriptor.GetConverter(property.PropertyType); dynamic typedValue = converter.CanConvertFrom(value.GetType()) ? converter.ConvertFrom(value) : Convert.ChangeType(value, property.PropertyType); // We generate our particle Type[] typeArgs = { typeof(TEntity), property.PropertyType }; Type byPropertyParticleType = typeof(ByPropertyParticle <,>).MakeGenericType(typeArgs); particle = (IFilterParticle <TEntity>)Activator.CreateInstance(byPropertyParticleType, property, op, typedValue); return(true); } catch { particle = null; return(false); } }