Lean  $LEAN_TAG$
FxcmBrokerageModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
19 using QuantConnect.Orders;
23 using QuantConnect.Util;
24 
26 {
27  /// <summary>
28  /// Provides FXCM specific properties
29  /// </summary>
31  {
32  /// <summary>
33  /// The default markets for the fxcm brokerage
34  /// </summary>
35  public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
36  {
37  {SecurityType.Base, Market.USA},
38  {SecurityType.Equity, Market.USA},
39  {SecurityType.Option, Market.USA},
40  {SecurityType.Forex, Market.FXCM},
41  {SecurityType.Cfd, Market.FXCM}
42  }.ToReadOnlyDictionary();
43 
44  private readonly HashSet<OrderType> _supportedOrderTypes = new()
45  {
46  OrderType.Limit,
47  OrderType.Market,
48  OrderType.StopMarket
49  };
50 
51  /// <summary>
52  /// Gets a map of the default markets to be used for each security type
53  /// </summary>
54  public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
55 
56  /// <summary>
57  /// Initializes a new instance of the <see cref="DefaultBrokerageModel"/> class
58  /// </summary>
59  /// <param name="accountType">The type of account to be modelled, defaults to
60  /// <see cref="AccountType.Margin"/></param>
61  public FxcmBrokerageModel(AccountType accountType = AccountType.Margin)
62  : base(accountType)
63  {
64  }
65 
66  /// <summary>
67  /// Returns true if the brokerage could accept this order. This takes into account
68  /// order type, security type, and order size limits.
69  /// </summary>
70  /// <remarks>
71  /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
72  /// </remarks>
73  /// <param name="security"></param>
74  /// <param name="order">The order to be processed</param>
75  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
76  /// <returns>True if the brokerage could process the order, false otherwise</returns>
77  public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
78  {
79  message = null;
80 
81  // validate security type
82  if (security.Type != SecurityType.Forex && security.Type != SecurityType.Cfd)
83  {
84  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
86 
87  return false;
88  }
89 
90  // validate order type
91  if (!_supportedOrderTypes.Contains(order.Type))
92  {
93  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
94  Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
95 
96  return false;
97  }
98 
99  // validate order quantity
100  if (order.Quantity % security.SymbolProperties.LotSize != 0)
101  {
102  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
104 
105  return false;
106  }
107 
108  // validate stop/limit orders prices
109  var limit = order as LimitOrder;
110  if (limit != null)
111  {
112  return IsValidOrderPrices(security, OrderType.Limit, limit.Direction, security.Price, limit.LimitPrice, ref message);
113  }
114 
115  var stopMarket = order as StopMarketOrder;
116  if (stopMarket != null)
117  {
118  return IsValidOrderPrices(security, OrderType.StopMarket, stopMarket.Direction, stopMarket.StopPrice, security.Price, ref message);
119  }
120 
121  // validate time in force
123  {
124  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
126 
127  return false;
128  }
129 
130  return true;
131  }
132 
133  /// <summary>
134  /// Returns true if the brokerage would allow updating the order as specified by the request
135  /// </summary>
136  /// <param name="security">The security of the order</param>
137  /// <param name="order">The order to be updated</param>
138  /// <param name="request">The requested update to be made to the order</param>
139  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
140  /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
141  public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
142  {
143  message = null;
144 
145  // validate order quantity
146  if (request.Quantity != null && request.Quantity % security.SymbolProperties.LotSize != 0)
147  {
148  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
150 
151  return false;
152  }
153 
154  // determine direction via the new, updated quantity
155  var newQuantity = request.Quantity ?? order.Quantity;
156  var direction = newQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
157 
158  // use security.Price if null, allows to pass checks
159  var stopPrice = request.StopPrice ?? security.Price;
160  var limitPrice = request.LimitPrice ?? security.Price;
161 
162  return IsValidOrderPrices(security, order.Type, direction, stopPrice, limitPrice, ref message);
163  }
164 
165  /// <summary>
166  /// Get the benchmark for this model
167  /// </summary>
168  /// <param name="securities">SecurityService to create the security with if needed</param>
169  /// <returns>The benchmark for this brokerage</returns>
170  public override IBenchmark GetBenchmark(SecurityManager securities)
171  {
172  var symbol = Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);
173  return SecurityBenchmark.CreateInstance(securities, symbol);
174  }
175 
176  /// <summary>
177  /// Gets a new fee model that represents this brokerage's fee structure
178  /// </summary>
179  /// <param name="security">The security to get a fee model for</param>
180  /// <returns>The new fee model for this brokerage</returns>
181  public override IFeeModel GetFeeModel(Security security)
182  {
183  return new FxcmFeeModel();
184  }
185 
186  /// <summary>
187  /// Gets a new settlement model for the security
188  /// </summary>
189  /// <param name="security">The security to get a settlement model for</param>
190  /// <returns>The settlement model for this brokerage</returns>
191  public override ISettlementModel GetSettlementModel(Security security)
192  {
193  return security.Type == SecurityType.Cfd
196  }
197 
198  /// <summary>
199  /// Validates limit/stopmarket order prices, pass security.Price for limit/stop if n/a
200  /// </summary>
201  private static bool IsValidOrderPrices(Security security, OrderType orderType, OrderDirection orderDirection, decimal stopPrice, decimal limitPrice, ref BrokerageMessageEvent message)
202  {
203  // validate order price
204  var invalidPrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice > security.Price ||
205  orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice < security.Price ||
206  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice < security.Price ||
207  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice > security.Price;
208 
209  if (invalidPrice)
210  {
211  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
213 
214  return false;
215  }
216 
217  // Validate FXCM maximum distance for limit and stop orders:
218  // there are two different Max Limits, 15000 pips and 50% rule,
219  // whichever comes first (for most pairs, 50% rule comes first)
220  var maxDistance = Math.Min(
221  // MinimumPriceVariation is 1/10th of a pip
222  security.SymbolProperties.MinimumPriceVariation * 10 * 15000,
223  security.Price / 2);
224  var currentPrice = security.Price;
225  var minPrice = currentPrice - maxDistance;
226  var maxPrice = currentPrice + maxDistance;
227 
228  var outOfRangePrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice < minPrice ||
229  orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice > maxPrice ||
230  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice > maxPrice ||
231  orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice < minPrice;
232 
233  if (outOfRangePrice)
234  {
235  var orderPrice = orderType == OrderType.Limit ? limitPrice : stopPrice;
236 
237  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
238  Messages.FxcmBrokerageModel.PriceOutOfRange(orderType, orderDirection, orderPrice, currentPrice));
239 
240  return false;
241  }
242 
243  return true;
244  }
245  }
246 }