{"action":"created","comment":{"url":"https://api.github.com/repos/QuantConnect/Lean/pulls/comments/39382203","id":39382203,"diff_hunk":"@@ -0,0 +1,1052 @@\n+﻿/*\n+ * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.\n+ * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.\n+ * \n+ * Licensed under the Apache License, Version 2.0 (the \"License\"); \n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n+ * \n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+*/\n+\n+using System;\n+using System.Collections.Concurrent;\n+using System.Collections.Generic;\n+using System.Globalization;\n+using System.IO;\n+using System.IO.Compression;\n+using System.Linq;\n+using System.Net;\n+using System.Runtime.Serialization.Json;\n+using System.Text;\n+using System.Text.RegularExpressions;\n+using System.Threading;\n+using System.Threading.Tasks;\n+using System.Xml;\n+using Newtonsoft.Json;\n+using QuantConnect.Brokerages.Oanda.DataType;\n+using QuantConnect.Brokerages.Oanda.DataType.Communications;\n+using QuantConnect.Brokerages.Oanda.Framework;\n+using QuantConnect.Brokerages.Oanda.Session;\n+using QuantConnect.Brokerages.Tradier;\n+using QuantConnect.Logging;\n+using QuantConnect.Orders;\n+using QuantConnect.Securities;\n+using RestSharp;\n+using Order = QuantConnect.Orders.Order;\n+\n+namespace QuantConnect.Brokerages.Oanda\n+{\n+    /// <summary>\n+    /// Oanda Brokerage model.\n+    /// </summary>\n+    public class OandaBrokerage : Brokerage\n+    {\n+        /// <summary>\n+        /// The account identifier for Oanda Accounts.\n+        /// </summary>\n+        public int AccountId { get; private set; }\n+\n+        private DateTime _issuedAt;\n+\n+        private TimeSpan _lifeSpan = TimeSpan.FromSeconds(86399); // 1 second less than a day\n+        \n+\n+        private readonly object _fillLock = new object();\n+\n+        /// <summary>\n+        /// Gets or sets the instrument security type map.\n+        /// </summary>\n+        /// <value>\n+        /// The instrument security type map.\n+        /// </value>\n+        public Dictionary<string, SecurityType> InstrumentSecurityTypeMap { get; private set; }\n+\n+        /// <summary>\n+        /// Gets the oanda environment.\n+        /// </summary>\n+        /// <value>\n+        /// The oanda environment.\n+        /// </value>\n+        public static Environment OandaEnvironment { get; private set; }\n+\n+        private readonly object _lockAccessCredentials = new object();\n+\n+        //This should correlate to the Trades/Positions list in Oanda.\n+        private readonly IHoldingsProvider _holdingsProvider;\n+\n+        //This should correlate to the Orders list in Oanda.\n+        private readonly IOrderProvider _orderProvider;\n+\n+        private readonly ConcurrentDictionary<long, DataType.Order> _cachedOpenOrdersByOandaOrderId;\n+\n+        /// <summary>\n+        /// The QC User id, used for refreshing the session\n+        /// </summary>\n+        public int UserId { get; private set; }\n+\n+        /// <summary>\n+        /// Access Token Access:\n+        /// </summary>\n+        public static string AccessToken { get; private set; }\n+\n+        /// <summary>\n+        /// Refresh Token Access:\n+        /// </summary>\n+        public string RefreshToken { get; private set; }\n+\n+\n+        /// <summary>\n+        /// Creates a new Brokerage instance with the specified name\n+        /// </summary>\n+        /// <param name=\"name\">The name of the brokerage</param>\n+        public OandaBrokerage(string name, int userId) : base(name)\n+        {\n+            UserId = userId;\n+        }\n+\n+        /// <summary>\n+        /// Initializes a new instance of the <see cref=\"OandaBrokerage\"/> class.\n+        /// </summary>\n+        /// <param name=\"orderProvider\">The order provider.</param>\n+        /// <param name=\"holdingsProvider\">The holdings provider.</param>\n+        /// <param name=\"accountId\">The account identifier.</param>\n+        public OandaBrokerage(IOrderProvider orderProvider, IHoldingsProvider holdingsProvider, int accountId)\n+            : base(\"Oanda Brokerage\")\n+        {\n+            _orderProvider = orderProvider;\n+            _holdingsProvider = holdingsProvider;\n+            AccountId = accountId;\n+            _cachedOpenOrdersByOandaOrderId = new ConcurrentDictionary<long, DataType.Order>();\n+            InstrumentSecurityTypeMap =  new Dictionary<string, SecurityType>(); \n+        }\n+\n+        /// <summary>\n+        /// Initializes the instrument security type map.\n+        /// </summary>\n+        public void InitializeInstrumentSecurityTypeMap()\n+        {\n+            InstrumentSecurityTypeMap = MapInstrumentToSecurityType(GetInstrumentsAsync());\n+        }\n+\n+        /// <summary>\n+        /// Returns true if we're currently connected to the broker\n+        /// </summary>\n+        public override bool IsConnected\n+        {\n+            get { return _issuedAt + _lifeSpan > DateTime.Now; }\n+        }\n+\n+        /// <summary>\n+        /// Gets all open orders on the account. \n+        /// NOTE: The order objects returned do not have QC order IDs.\n+        /// </summary>\n+        /// <returns>The open orders returned from Oanda</returns>\n+        public override List<Order> GetOpenOrders()\n+        {\n+            var oandaOrders = GetOrderList();\n+\n+            var orderList = oandaOrders.Select(ConvertOrder).ToList();\n+            foreach (var openOrder in orderList)\n+            {\n+                _cachedOpenOrdersByOandaOrderId[openOrder.Id] = oandaOrders.FirstOrDefault(oo => oo.id == openOrder.Id);\n+            }\n+            return orderList;\n+        }\n+\n+        /// <summary>\n+        /// Gets all holdings for the account\n+        /// </summary>\n+        /// <returns>The current holdings from the account</returns>\n+        public override List<Holding> GetAccountHoldings()\n+        {\n+            var holdings = GetPositions().Select(ConvertHolding).ToList();\n+            return holdings;\n+        }\n+\n+        /// <summary>\n+        /// Converts the Oanda position into a QuantConnect holding.\n+        /// </summary>\n+        /// <param name=\"position\">The position.</param>\n+        /// <returns></returns>\n+        protected Holding ConvertHolding(Position position)\n+        {\n+            return new Holding\n+            {\n+                Symbol = position.instrument,\n+                Type = InstrumentSecurityTypeMap[position.instrument],\n+                AveragePrice = (decimal)position.avgPrice,\n+                ConversionRate = 1.0m,\n+                CurrencySymbol = \"$\",\n+                Quantity = position.side == \"sell\" ? -position.units : position.units\n+            };\n+        }\n+\n+\n+\n+        /// <summary>\n+        /// Gets the current cash balance for each currency held in the brokerage account\n+        /// </summary>\n+        /// <returns>The current cash balance for each currency available for trading</returns>\n+        public override List<Cash> GetCashBalance()\n+        {\n+            var cash = new List<Cash>();\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId;\n+            var accountResponse = MakeRequest<Account>(requestString);\n+            //TODO figure how exchange rates work in Oanda. probably need to call http://developer.oanda.com/exchange-rates-api/v1/currencies/\n+            cash.Add(new Cash(accountResponse.accountCurrency, accountResponse.balance.ToDecimal(), new decimal(1.0)));\n+            return cash;\n+        }\n+        \n+        private static string GetCommaSeparatedList(IEnumerable<string> items)\n+        {\n+            var result = new StringBuilder();\n+            foreach (var item in items)\n+            {\n+                result.Append(item + \",\");\n+            }\n+            return result.ToString().Trim(',');\n+        }\n+\n+\n+        private Dictionary<string, SecurityType> MapInstrumentToSecurityType(List<Instrument> instruments)\n+        {\n+            var result = new Dictionary<string, SecurityType>();\n+            foreach (var instrument in instruments)\n+            {\n+                var isForex = Regex.IsMatch(instrument.instrument, \"[A-Z]{3}_[A-Z]{3}\") && Regex.IsMatch(instrument.displayName, \"[A-Z]{3}/[A-Z]{3}\");\n+                result.Add(instrument.instrument, isForex ? SecurityType.Forex : SecurityType.Cfd);\n+            }\n+            return result;\n+        }\n+\n+        /// <summary>\n+        /// Gets the list of available tradable instruments/products from Oanda\n+        /// </summary>\n+        /// <returns></returns>\n+        public List<Instrument> GetInstrumentsAsync(List<string> instrumentNames = null)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Rates) + \"instruments?accountId=\" + AccountId;\n+            if (instrumentNames != null)\n+            {\n+                var instrumentsParam = GetCommaSeparatedList(instrumentNames);\n+                requestString += \"&instruments=\" + Uri.EscapeDataString(instrumentsParam);\n+            }\n+            var instrumentResponse = MakeRequest<InstrumentsResponse>(requestString);\n+            var instruments = new List<Instrument>();\n+            instruments.AddRange(instrumentResponse.instruments);\n+            return instruments;\n+        }\n+\n+        /// <summary>\n+        /// Places a new order and assigns a new broker ID to the order\n+        /// </summary>\n+        /// <param name=\"order\">The order to be placed</param>\n+        /// <returns>True if the request for a new order has been placed, false otherwise</returns>\n+        public override bool PlaceOrder(Order order)\n+        {\n+            var requestParams = new Dictionary<string, string>\n+            {\n+                {\"instrument\", order.Symbol},\n+                {\"units\", Convert.ToInt32(order.AbsoluteQuantity).ToString()}\n+            };\n+\n+            PopulateOrderRequestParameters(order, requestParams);\n+\n+            Log.Trace(string.Format(\"OandaBrokerage.PlaceOrder(): {0} to {1} {2} units of {3}\", order.Type, order.Direction, order.Quantity, order.Symbol));\n+\n+\n+            var priorOrderPositions = GetTradeList(requestParams);\n+\n+            var postOrderResponse = PostOrderAsync(requestParams);\n+\n+            if (postOrderResponse != null)\n+            {\n+                if (postOrderResponse.tradeOpened != null)\n+                {\n+                    order.BrokerId.Add(postOrderResponse.tradeOpened.id);\n+                }\n+                \n+                if (postOrderResponse.tradeReduced != null)\n+                {\n+                    order.BrokerId.Add(postOrderResponse.tradeReduced.id);\n+                }\n+\n+                if (postOrderResponse.orderOpened != null)\n+                {\n+                    order.BrokerId.Add(postOrderResponse.orderOpened.id);\n+                }\n+\n+                OnOrderEvent(new OrderEvent(order) { Status = OrderStatus.Submitted });\n+            } \n+            else\n+            {\n+                return false;\n+            }\n+\n+            // we need to determine if there was an existing order and wheter we closed it with market orders.\n+\n+            if (order.Type == OrderType.Market && order.Direction == OrderDirection.Buy)\n+            {\n+                //assume that we are opening a new buy market order\n+                if (postOrderResponse.tradeOpened != null && postOrderResponse.tradeOpened.id > 0)\n+                {\n+                    var tradeOpenedId = postOrderResponse.tradeOpened.id;\n+                    requestParams = new Dictionary<string, string>();\n+                    var tradeListResponse = GetTradeList(requestParams);\n+                    if (tradeListResponse.trades.Any(trade => trade.id == tradeOpenedId))\n+                    {\n+                        order.BrokerId.Add(tradeOpenedId);\n+                        OnOrderEvent(new OrderEvent(order) { Status = OrderStatus.Filled });\n+\n+                    }\n+                }\n+\n+                if (postOrderResponse.tradesClosed != null)\n+                {\n+                    var tradePositionClosedIds = postOrderResponse.tradesClosed.Select(tradesClosed => tradesClosed.id).ToList();\n+                    var priorOrderPositionIds = priorOrderPositions.trades.Select(previousTrade => previousTrade.id).ToList();\n+                    var verifyClosedOrder = tradePositionClosedIds.Intersect(priorOrderPositionIds).Count() == tradePositionClosedIds.Count();\n+                    if (verifyClosedOrder)\n+                    {\n+                        OnOrderEvent(new OrderEvent(order) { Status = OrderStatus.Filled });\n+                    }\n+                }\n+            }\n+\n+            if (order.Type == OrderType.Market && order.Direction == OrderDirection.Sell)\n+            {                \n+                //assume that we are opening a new buy market order\n+                if (postOrderResponse.tradeOpened != null && postOrderResponse.tradeOpened.id > 0)\n+                {\n+                    var tradeOpenedId = postOrderResponse.tradeOpened.id;\n+                    requestParams = new Dictionary<string, string>();\n+                    var tradeListResponse = GetTradeList(requestParams);\n+                    if (tradeListResponse.trades.Any(trade => trade.id == tradeOpenedId))\n+                    {\n+                        order.BrokerId.Add(tradeOpenedId);\n+                        OnOrderEvent(new OrderEvent(order) { Status = OrderStatus.Filled });\n+                    }\n+                }\n+\n+                if (postOrderResponse.tradesClosed != null)\n+                {\n+                    var tradePositionClosedIds = postOrderResponse.tradesClosed.Select(tradesClosed => tradesClosed.id).ToList();\n+                    var priorOrderPositionIds = priorOrderPositions.trades.Select(previousTrade => previousTrade.id).ToList();\n+                    var verifyClosedOrder = tradePositionClosedIds.Intersect(priorOrderPositionIds).Count() == tradePositionClosedIds.Count();\n+                    if (verifyClosedOrder)\n+                    {\n+                        OnOrderEvent(new OrderEvent(order) { Status = OrderStatus.Filled });   \n+                    }\n+                }\n+            }\n+            return true;\n+        }\n+\n+\n+        /// <summary>\n+        /// Updates the order with the same id\n+        /// </summary>\n+        /// <param name=\"order\">The new order information</param>\n+        /// <returns>True if the request was made for the order to be updated, false otherwise</returns>\n+        public override bool UpdateOrder(Order order)\n+        {\n+            Log.Trace(\"OandaBrokerage.UpdateOrder(): \" + order);\n+            \n+            if (!order.BrokerId.Any())\n+            {\n+                // we need the brokerage order id in order to perform an update\n+                Log.Trace(\"OandaBrokerage.UpdateOrder(): Unable to update order without BrokerId.\");\n+                return false;\n+            }\n+            \n+            var requestParams = new Dictionary<string, string>\n+            {\n+                {\"instrument\", order.Symbol},\n+                {\"units\", Convert.ToInt32(order.AbsoluteQuantity).ToString()},\n+            };\n+\n+            // we need the brokerage order id in order to perform an update\n+            PopulateOrderRequestParameters(order, requestParams);\n+\n+            UpdateOrder(order.BrokerId.First(), requestParams);\n+\n+            return true;\n+        }\n+\n+        private static void PopulateOrderRequestParameters(Order order, Dictionary<string, string> requestParams)\n+        {\n+            if (order.Direction != OrderDirection.Buy && order.Direction != OrderDirection.Sell)\n+            {\n+                throw new Exception(\"Invalid Order Direction\");\n+            }\n+\n+            requestParams.Add(\"side\", order.Direction == OrderDirection.Buy ? \"buy\" : \"sell\");\n+\n+            if (order.Type == OrderType.Market)\n+            {\n+                requestParams.Add(\"type\", \"market\");\n+            }\n+\n+            if (order.Type == OrderType.Limit)\n+            {\n+                requestParams.Add(\"type\", \"limit\");\n+                requestParams.Add(\"price\", ((LimitOrder)order).LimitPrice.ToString(CultureInfo.InvariantCulture));\n+                switch (order.Direction)\n+                {\n+                    case OrderDirection.Buy:\n+                        //Limit Order Does not like Lower Bound Values == Limit Price value\n+                        //Don't set bounds when placing limit orders. \n+                        //Orders can be submitted with lower and upper bounds. If the market price on execution falls outside these bounds, it is considered a \"Bounds Violation\" and the order is cancelled.\n+                         break;\n+\n+                    case OrderDirection.Sell:\n+                        //Limit Order Does not like Lower Bound Values == Limit Price value\n+                        //Don't set bounds when placing limit orders. \n+                        //Orders can be submitted with lower and upper bounds. If the market price on execution falls outside these bounds, it is considered a \"Bounds Violation\" and the order is cancelled.\n+                        break;\n+                }\n+\n+                //3 months is the max expiry for Oanda, and OrderDuration.GTC is only currently available\n+                requestParams.Add(\"expiry\", XmlConvert.ToString(DateTime.Now.AddMonths(3), XmlDateTimeSerializationMode.Utc));\n+            }\n+\n+            //this type should contain a stop and a limit to that stop.\n+            if (order.Type == OrderType.StopLimit)\n+            {\n+                requestParams.Add(\"type\", \"stop\");\n+                requestParams.Add(\"price\", ((StopLimitOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture));\n+                switch (order.Direction)\n+                {\n+                    case OrderDirection.Buy:\n+                        //Orders can be submitted with lower and upper bounds. If the market price on execution falls outside these bounds, it is considered a \"Bounds Violation\" and the order is cancelled.\n+                        break;\n+                    case OrderDirection.Sell:\n+                        //Orders can be submitted with lower and upper bounds. If the market price on execution falls outside these bounds, it is considered a \"Bounds Violation\" and the order is cancelled.\n+                        break;\n+                }\n+\n+                //3 months is the max expiry for Oanda, and OrderDuration.GTC is only currently available\n+                requestParams.Add(\"expiry\", XmlConvert.ToString(DateTime.Now.AddMonths(3), XmlDateTimeSerializationMode.Utc));\n+            }\n+\n+            if (order.Type == OrderType.StopMarket)\n+            {\n+                requestParams.Add(\"type\", \"marketIfTouched\");\n+                requestParams.Add(\"price\", ((StopMarketOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture));\n+                switch (order.Direction)\n+                {\n+                    case OrderDirection.Buy:\n+                        requestParams.Add(\"upperBound\", ((StopMarketOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture));\n+                        break;\n+                    case OrderDirection.Sell:\n+                        requestParams.Add(\"lowerBound\", ((StopMarketOrder)order).StopPrice.ToString(CultureInfo.InvariantCulture));\n+                        break;\n+                }\n+\n+                //3 months is the max expiry for Oanda, and OrderDuration.GTC is only currently available\n+                requestParams.Add(\"expiry\", XmlConvert.ToString(DateTime.Now.AddMonths(3), XmlDateTimeSerializationMode.Utc));\n+            }\n+        }\n+\n+\n+        /// <summary>\n+        /// Checks for fill events by registering to the event session to receive events.\n+        /// </summary>\n+        private void CheckForFills()\n+        {\n+            // reentrance gaurd\n+            if (!Monitor.TryEnter(_fillLock))\n+            {\n+                return;\n+            }\n+            try\n+            {\n+                var session = new EventsSession(AccountId);\n+                session.DataReceived += OnEventReceived;\n+                session.StartSession();\n+            }\n+            finally\n+            {\n+                Monitor.Exit(_fillLock);\n+            }\n+        }\n+\n+        private void OnEventReceived(Event data)\n+        {\n+            #if DEBUG\n+            Console.Out.Write(\"---- On Event Received ----\");\n+            Console.Out.Write(data.transaction);\n+            #endif\n+            \n+            if (data.transaction != null)\n+            {\n+                if (data.transaction.type == \"ORDER_FILLED\")\n+                {\n+                    var qcOrder = _orderProvider.GetOrderByBrokerageId(data.transaction.orderId);\n+                    var fill = new OrderEvent(qcOrder, \"Oanda Fill Event\")\n+                    {\n+                        Status = OrderStatus.Filled,\n+                        //    // this is guaranteed to be wrong in the event we have multiple fills within our polling interval,\n+                        //    // we're able to partially cope with the fill quantity by diffing the previous info vs current info\n+                        //    // but the fill price will always be the most recent fill, so if we have two fills with 1/10 of a second\n+                        //    // we'll get the latter fill price, so for large orders this can lead to inconsistent state\n+                        FillPrice = (decimal) data.transaction.price,\n+                        FillQuantity = data.transaction.units\n+                    };\n+\n+                    // flip the quantity on sell actions\n+                    if (qcOrder.Direction == OrderDirection.Sell)\n+                    {\n+                        fill.FillQuantity *= -1;\n+                    }\n+                    OnOrderEvent(fill);\n+                }\n+            }\n+        }\n+\n+        /// <summary>\n+        /// Obtain the active open Trade List from Oanda.\n+        /// </summary>\n+        /// <param name=\"requestParams\"></param>\n+        /// <returns></returns>\n+        public TradesResponse GetTradeList(Dictionary<string, string> requestParams = null)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/trades\";\n+            return MakeRequest<TradesResponse>(requestString, \"GET\", requestParams);\n+        }\n+        \n+        /// <summary>\n+        /// Modify the specified order, updating it with the parameters provided\n+        /// </summary>\n+        /// <param name=\"orderId\">the identifier of the order to update</param>\n+        /// <param name=\"requestParams\">the parameters to update (name, value pairs)</param>\n+        public void UpdateOrder(long orderId, Dictionary<string, string> requestParams)\n+        {\n+            var orderRequest = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders/\" + orderId;\n+\n+            var order = MakeRequest<DataType.Order>(orderRequest);\n+            if (order != null && order.id > 0)\n+            {\n+                var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders/\" + orderId;\n+                try\n+                {\n+                    MakeRequestWithBody<DataType.Order>(requestString, \"PATCH\", requestParams);\n+                     \n+                } \n+                catch (Exception)\n+                {\n+                }\n+                \n+            } \n+            else\n+            {\n+\n+                OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, \"UpdateFailed\", \"Failed to update Oanda order id: \" + orderId + \".\"));\n+            }\n+        }\n+\n+        /// <summary>\n+        /// Retrieves the details for a given order ID\n+        /// </summary>\n+        /// <param name=\"orderId\">the id of the order to retrieve</param>\n+        /// <returns>Order object containing the order details</returns>\n+        public DataType.Order GetOrderDetails(int orderId)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders/\" + orderId;\n+            var order = MakeRequest<DataType.Order>(requestString);\n+            return order;\n+        }\n+\n+        /// <summary>\n+        /// Retrieves the current rate for each of a list of instruments\n+        /// </summary>\n+        /// <param name=\"instruments\">the list of instruments to check</param>\n+        /// <returns>List of Price objects with the current price for each instrument</returns>\n+        public List<Price> GetRates(List<Instrument> instruments)\n+        {\n+            var requestBuilder = new StringBuilder(EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Rates) + \"prices?instruments=\");\n+\n+            foreach (var instrument in instruments)\n+            {\n+                requestBuilder.Append(instrument.instrument + \",\");\n+            }\n+            var requestString = requestBuilder.ToString().Trim(',');\n+            requestString = requestString.Replace(\",\", \"%2C\");\n+\n+            var pricesResponse = MakeRequest<PricesResponse>(requestString);\n+            var prices = new List<Price>();\n+            prices.AddRange(pricesResponse.prices);\n+\n+            return prices;\n+        }\n+\n+        /// <summary>\n+        /// Posts an order on the given account with the given parameters\n+        /// </summary>\n+        /// <param name=\"requestParams\">the parameters to use in the request</param>\n+        /// <returns>PostOrderResponse with details of the results (throws if if fails)</returns>\n+        public PostOrderResponse PostOrderAsync(Dictionary<string, string> requestParams)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders\";\n+            return MakeRequestWithBody<PostOrderResponse>(requestString, \"POST\", requestParams);\n+        }\n+\n+        /// <summary>\n+        /// Retrieves the list of open orders belonging to the account\n+        /// </summary>\n+        /// <param name=\"requestParams\">optional additional parameters for the request (name, value pairs)</param>\n+        /// <returns>List of Order objects (or empty list, if no orders)</returns>\n+        public List<DataType.Order> GetOrderList(Dictionary<string, string> requestParams = null)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders\";\n+            var ordersResponse = MakeRequest<OrdersResponse>(requestString, \"GET\", requestParams);\n+            var orders = new List<DataType.Order>();\n+            orders.AddRange(ordersResponse.orders);\n+            return orders;\n+        }\n+\n+        /// <summary>\n+        /// Cancels the order with the specified ID\n+        /// </summary>\n+        /// <param name=\"order\">The order to cancel</param>\n+        /// <returns>True if the request was made for the order to be canceled, false otherwise</returns>\n+        public override bool CancelOrder(Order order)\n+        {\n+            Log.Trace(\"OandaBrokerage.CancelOrder(): \" + order);\n+            \n+            if (!order.BrokerId.Any())\n+            {\n+                Log.Trace(\"OandaBrokerage.CancelOrder(): Unable to cancel order without BrokerId.\");\n+                return false;\n+            }\n+\n+            foreach (var orderId in order.BrokerId)\n+            {\n+                CancelOrderAsync(orderId);\n+                OnOrderEvent(new OrderEvent(order, \"Oanda Cancel Order Event\") { Status = OrderStatus.Canceled });\n+            }\n+\n+            return true;\n+        }\n+\n+        private void CancelOrderAsync(long orderId)\n+        {\n+            var requestString = EndpointResolver.ResolveEndpoint(OandaEnvironment, Server.Account) + \"accounts/\" + AccountId + \"/orders/\" + orderId;\n+            var response = MakeRequestAsync<Order>(requestString, \"DELETE\");\n+        }\n+        \n+\n+        /// <summary>\n+        /// Verify we have a user session; or refresh the access token.\n+        /// </summary>\n+        public bool RefreshSession()\n+        {\n+            var raw = \"\";\n+            bool success;\n+            lock (_lockAccessCredentials)\n+            {\n+                //Create the client for connection:\n+                var client = new RestClient(\"https://www.quantconnect.com/terminal/\");\n+\n+                //Create the GET call:\n+                var request = new RestRequest(\"processOanda\", Method.GET);\n+                request.AddParameter(\"uid\", UserId.ToString(), ParameterType.GetOrPost);\n+                request.AddParameter(\"accessToken\", AccessToken, ParameterType.GetOrPost);\n+                request.AddParameter(\"refreshToken\", RefreshToken, ParameterType.GetOrPost);\n+\n+                //Submit the call:\n+                var result = client.Execute(request);\n+                raw = result.Content;\n+\n+                //Decode to token response: update internal access parameters:\n+                var newTokens = JsonConvert.DeserializeObject<TokenResponse>(result.Content); \n+                if (newTokens != null && newTokens.Success)\n+                {\n+                    AccessToken = newTokens.AccessToken;\n+                    RefreshToken = newTokens.RefreshToken;\n+                    _issuedAt = newTokens.IssuedAt;\n+                    _lifeSpan = TimeSpan.FromSeconds(newTokens.ExpiresIn);\n+                    Log.Trace(\"SESSION REFRESHED: Access: \" + AccessToken + \" Refresh: \" + RefreshToken + \" Issued At: \" + _lifeSpan + \" JSON>>\"\n+                        + result.Content);\n+                    OnSessionRefreshed(newTokens);\n+                    success = true;\n+                } \n+                else\n+                {\n+                    Log.Error(\"Oanda.RefreshSession(): Error Refreshing Session: URL: \" + client.BuildUri(request) + \" Response: \" + result.Content);\n+                    success = false;\n+                }\n+            }\n+\n+            if (!success)\n+            {\n+                // if we can't refresh our tokens then we must stop the algorithm\n+                OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, \"RefreshSession\", \"Failed to refresh access token: \" + raw));\n+            }\n+\n+            return success;\n+        }\n+\n+        /// <summary>\n+        /// Event invocator for the SessionRefreshed event\n+        /// </summary>\n+        protected virtual void OnSessionRefreshed(TokenResponse e)\n+        {\n+            var handler = SessionRefreshed;\n+            if (handler != null) handler(this, e);\n+        }\n+\n+        /// <summary>\n+        /// Connects the client to the broker's remote servers\n+        /// </summary>\n+        public override void Connect()\n+        {\n+            if (IsConnected) return;\n+            RefreshSession();\n+        }\n+\n+        /// <summary>\n+        /// Disconnects the client from the broker's remote servers\n+        /// </summary>\n+        public override void Disconnect()\n+        {\n+            throw new NotImplementedException();","path":"Brokerages/Oanda/OandaBrokerage.cs","position":668,"original_position":718,"commit_id":"eef120901831d4bc2eb3a2cddc6b7b50732f4cca","original_commit_id":"f468420964005b5bf3ed2f9395479ce2b465c904","user":{"login":"kaffeebrauer","id":5925601,"avatar_url":"https://avatars.githubusercontent.com/u/5925601?v=3","gravatar_id":"","url":"https://api.github.com/users/kaffeebrauer","html_url":"https://github.com/kaffeebrauer","followers_url":"https://api.github.com/users/kaffeebrauer/followers","following_url":"https://api.github.com/users/kaffeebrauer/following{/other_user}","gists_url":"https://api.github.com/users/kaffeebrauer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaffeebrauer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaffeebrauer/subscriptions","organizations_url":"https://api.github.com/users/kaffeebrauer/orgs","repos_url":"https://api.github.com/users/kaffeebrauer/repos","events_url":"https://api.github.com/users/kaffeebrauer/events{/privacy}","received_events_url":"https://api.github.com/users/kaffeebrauer/received_events","type":"User","site_admin":false},"body":"Agreed, removed the Exception thrown. Non-event streaming API of Oanda does not disconnect per se. ","created_at":"2015-09-14T11:08:04Z","updated_at":"2015-09-14T11:08:04Z","html_url":"https://github.com/QuantConnect/Lean/pull/152#discussion_r39382203","pull_request_url":"https://api.github.com/repos/QuantConnect/Lean/pulls/152","_links":{"self":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/comments/39382203"},"html":{"href":"https://github.com/QuantConnect/Lean/pull/152#discussion_r39382203"},"pull_request":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/152"}}},"pull_request":{"url":"https://api.github.com/repos/QuantConnect/Lean/pulls/152","id":42195752,"html_url":"https://github.com/QuantConnect/Lean/pull/152","diff_url":"https://github.com/QuantConnect/Lean/pull/152.diff","patch_url":"https://github.com/QuantConnect/Lean/pull/152.patch","issue_url":"https://api.github.com/repos/QuantConnect/Lean/issues/152","number":152,"state":"open","locked":false,"title":"Add OANDA Brokerage","user":{"login":"kaffeebrauer","id":5925601,"avatar_url":"https://avatars.githubusercontent.com/u/5925601?v=3","gravatar_id":"","url":"https://api.github.com/users/kaffeebrauer","html_url":"https://github.com/kaffeebrauer","followers_url":"https://api.github.com/users/kaffeebrauer/followers","following_url":"https://api.github.com/users/kaffeebrauer/following{/other_user}","gists_url":"https://api.github.com/users/kaffeebrauer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaffeebrauer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaffeebrauer/subscriptions","organizations_url":"https://api.github.com/users/kaffeebrauer/orgs","repos_url":"https://api.github.com/users/kaffeebrauer/repos","events_url":"https://api.github.com/users/kaffeebrauer/events{/privacy}","received_events_url":"https://api.github.com/users/kaffeebrauer/received_events","type":"User","site_admin":false},"body":"https://github.com/QuantConnect/Lean/issues/19","created_at":"2015-08-12T01:13:11Z","updated_at":"2015-09-14T11:08:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8a76609571f39d0e66d37a344a3e29afcbb7227a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/QuantConnect/Lean/pulls/152/commits","review_comments_url":"https://api.github.com/repos/QuantConnect/Lean/pulls/152/comments","review_comment_url":"https://api.github.com/repos/QuantConnect/Lean/pulls/comments{/number}","comments_url":"https://api.github.com/repos/QuantConnect/Lean/issues/152/comments","statuses_url":"https://api.github.com/repos/QuantConnect/Lean/statuses/eef120901831d4bc2eb3a2cddc6b7b50732f4cca","head":{"label":"kaffeebrauer:master","ref":"master","sha":"eef120901831d4bc2eb3a2cddc6b7b50732f4cca","user":{"login":"kaffeebrauer","id":5925601,"avatar_url":"https://avatars.githubusercontent.com/u/5925601?v=3","gravatar_id":"","url":"https://api.github.com/users/kaffeebrauer","html_url":"https://github.com/kaffeebrauer","followers_url":"https://api.github.com/users/kaffeebrauer/followers","following_url":"https://api.github.com/users/kaffeebrauer/following{/other_user}","gists_url":"https://api.github.com/users/kaffeebrauer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaffeebrauer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaffeebrauer/subscriptions","organizations_url":"https://api.github.com/users/kaffeebrauer/orgs","repos_url":"https://api.github.com/users/kaffeebrauer/repos","events_url":"https://api.github.com/users/kaffeebrauer/events{/privacy}","received_events_url":"https://api.github.com/users/kaffeebrauer/received_events","type":"User","site_admin":false},"repo":{"id":38241962,"name":"Lean","full_name":"kaffeebrauer/Lean","owner":{"login":"kaffeebrauer","id":5925601,"avatar_url":"https://avatars.githubusercontent.com/u/5925601?v=3","gravatar_id":"","url":"https://api.github.com/users/kaffeebrauer","html_url":"https://github.com/kaffeebrauer","followers_url":"https://api.github.com/users/kaffeebrauer/followers","following_url":"https://api.github.com/users/kaffeebrauer/following{/other_user}","gists_url":"https://api.github.com/users/kaffeebrauer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaffeebrauer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaffeebrauer/subscriptions","organizations_url":"https://api.github.com/users/kaffeebrauer/orgs","repos_url":"https://api.github.com/users/kaffeebrauer/repos","events_url":"https://api.github.com/users/kaffeebrauer/events{/privacy}","received_events_url":"https://api.github.com/users/kaffeebrauer/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kaffeebrauer/Lean","description":"Lean Algorithmic Trading Engine by QuantConnect (C#, F#, VB, Java)","fork":true,"url":"https://api.github.com/repos/kaffeebrauer/Lean","forks_url":"https://api.github.com/repos/kaffeebrauer/Lean/forks","keys_url":"https://api.github.com/repos/kaffeebrauer/Lean/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kaffeebrauer/Lean/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kaffeebrauer/Lean/teams","hooks_url":"https://api.github.com/repos/kaffeebrauer/Lean/hooks","issue_events_url":"https://api.github.com/repos/kaffeebrauer/Lean/issues/events{/number}","events_url":"https://api.github.com/repos/kaffeebrauer/Lean/events","assignees_url":"https://api.github.com/repos/kaffeebrauer/Lean/assignees{/user}","branches_url":"https://api.github.com/repos/kaffeebrauer/Lean/branches{/branch}","tags_url":"https://api.github.com/repos/kaffeebrauer/Lean/tags","blobs_url":"https://api.github.com/repos/kaffeebrauer/Lean/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kaffeebrauer/Lean/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kaffeebrauer/Lean/git/refs{/sha}","trees_url":"https://api.github.com/repos/kaffeebrauer/Lean/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kaffeebrauer/Lean/statuses/{sha}","languages_url":"https://api.github.com/repos/kaffeebrauer/Lean/languages","stargazers_url":"https://api.github.com/repos/kaffeebrauer/Lean/stargazers","contributors_url":"https://api.github.com/repos/kaffeebrauer/Lean/contributors","subscribers_url":"https://api.github.com/repos/kaffeebrauer/Lean/subscribers","subscription_url":"https://api.github.com/repos/kaffeebrauer/Lean/subscription","commits_url":"https://api.github.com/repos/kaffeebrauer/Lean/commits{/sha}","git_commits_url":"https://api.github.com/repos/kaffeebrauer/Lean/git/commits{/sha}","comments_url":"https://api.github.com/repos/kaffeebrauer/Lean/comments{/number}","issue_comment_url":"https://api.github.com/repos/kaffeebrauer/Lean/issues/comments{/number}","contents_url":"https://api.github.com/repos/kaffeebrauer/Lean/contents/{+path}","compare_url":"https://api.github.com/repos/kaffeebrauer/Lean/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kaffeebrauer/Lean/merges","archive_url":"https://api.github.com/repos/kaffeebrauer/Lean/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kaffeebrauer/Lean/downloads","issues_url":"https://api.github.com/repos/kaffeebrauer/Lean/issues{/number}","pulls_url":"https://api.github.com/repos/kaffeebrauer/Lean/pulls{/number}","milestones_url":"https://api.github.com/repos/kaffeebrauer/Lean/milestones{/number}","notifications_url":"https://api.github.com/repos/kaffeebrauer/Lean/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kaffeebrauer/Lean/labels{/name}","releases_url":"https://api.github.com/repos/kaffeebrauer/Lean/releases{/id}","created_at":"2015-06-29T10:29:23Z","updated_at":"2015-06-29T10:29:25Z","pushed_at":"2015-09-07T22:13:33Z","git_url":"git://github.com/kaffeebrauer/Lean.git","ssh_url":"git@github.com:kaffeebrauer/Lean.git","clone_url":"https://github.com/kaffeebrauer/Lean.git","svn_url":"https://github.com/kaffeebrauer/Lean","homepage":"https://lean.quantconnect.com","size":66934,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"QuantConnect:master","ref":"master","sha":"24a10d05fd0ec8e19063823aa0a4accdaa0a7f30","user":{"login":"QuantConnect","id":3912814,"avatar_url":"https://avatars.githubusercontent.com/u/3912814?v=3","gravatar_id":"","url":"https://api.github.com/users/QuantConnect","html_url":"https://github.com/QuantConnect","followers_url":"https://api.github.com/users/QuantConnect/followers","following_url":"https://api.github.com/users/QuantConnect/following{/other_user}","gists_url":"https://api.github.com/users/QuantConnect/gists{/gist_id}","starred_url":"https://api.github.com/users/QuantConnect/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QuantConnect/subscriptions","organizations_url":"https://api.github.com/users/QuantConnect/orgs","repos_url":"https://api.github.com/users/QuantConnect/repos","events_url":"https://api.github.com/users/QuantConnect/events{/privacy}","received_events_url":"https://api.github.com/users/QuantConnect/received_events","type":"Organization","site_admin":false},"repo":{"id":27251463,"name":"Lean","full_name":"QuantConnect/Lean","owner":{"login":"QuantConnect","id":3912814,"avatar_url":"https://avatars.githubusercontent.com/u/3912814?v=3","gravatar_id":"","url":"https://api.github.com/users/QuantConnect","html_url":"https://github.com/QuantConnect","followers_url":"https://api.github.com/users/QuantConnect/followers","following_url":"https://api.github.com/users/QuantConnect/following{/other_user}","gists_url":"https://api.github.com/users/QuantConnect/gists{/gist_id}","starred_url":"https://api.github.com/users/QuantConnect/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QuantConnect/subscriptions","organizations_url":"https://api.github.com/users/QuantConnect/orgs","repos_url":"https://api.github.com/users/QuantConnect/repos","events_url":"https://api.github.com/users/QuantConnect/events{/privacy}","received_events_url":"https://api.github.com/users/QuantConnect/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/QuantConnect/Lean","description":"Lean Algorithmic Trading Engine by QuantConnect (C#, Python, F#, VB, Java)","fork":false,"url":"https://api.github.com/repos/QuantConnect/Lean","forks_url":"https://api.github.com/repos/QuantConnect/Lean/forks","keys_url":"https://api.github.com/repos/QuantConnect/Lean/keys{/key_id}","collaborators_url":"https://api.github.com/repos/QuantConnect/Lean/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/QuantConnect/Lean/teams","hooks_url":"https://api.github.com/repos/QuantConnect/Lean/hooks","issue_events_url":"https://api.github.com/repos/QuantConnect/Lean/issues/events{/number}","events_url":"https://api.github.com/repos/QuantConnect/Lean/events","assignees_url":"https://api.github.com/repos/QuantConnect/Lean/assignees{/user}","branches_url":"https://api.github.com/repos/QuantConnect/Lean/branches{/branch}","tags_url":"https://api.github.com/repos/QuantConnect/Lean/tags","blobs_url":"https://api.github.com/repos/QuantConnect/Lean/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/QuantConnect/Lean/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/QuantConnect/Lean/git/refs{/sha}","trees_url":"https://api.github.com/repos/QuantConnect/Lean/git/trees{/sha}","statuses_url":"https://api.github.com/repos/QuantConnect/Lean/statuses/{sha}","languages_url":"https://api.github.com/repos/QuantConnect/Lean/languages","stargazers_url":"https://api.github.com/repos/QuantConnect/Lean/stargazers","contributors_url":"https://api.github.com/repos/QuantConnect/Lean/contributors","subscribers_url":"https://api.github.com/repos/QuantConnect/Lean/subscribers","subscription_url":"https://api.github.com/repos/QuantConnect/Lean/subscription","commits_url":"https://api.github.com/repos/QuantConnect/Lean/commits{/sha}","git_commits_url":"https://api.github.com/repos/QuantConnect/Lean/git/commits{/sha}","comments_url":"https://api.github.com/repos/QuantConnect/Lean/comments{/number}","issue_comment_url":"https://api.github.com/repos/QuantConnect/Lean/issues/comments{/number}","contents_url":"https://api.github.com/repos/QuantConnect/Lean/contents/{+path}","compare_url":"https://api.github.com/repos/QuantConnect/Lean/compare/{base}...{head}","merges_url":"https://api.github.com/repos/QuantConnect/Lean/merges","archive_url":"https://api.github.com/repos/QuantConnect/Lean/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/QuantConnect/Lean/downloads","issues_url":"https://api.github.com/repos/QuantConnect/Lean/issues{/number}","pulls_url":"https://api.github.com/repos/QuantConnect/Lean/pulls{/number}","milestones_url":"https://api.github.com/repos/QuantConnect/Lean/milestones{/number}","notifications_url":"https://api.github.com/repos/QuantConnect/Lean/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/QuantConnect/Lean/labels{/name}","releases_url":"https://api.github.com/repos/QuantConnect/Lean/releases{/id}","created_at":"2014-11-28T04:20:19Z","updated_at":"2015-09-11T09:05:34Z","pushed_at":"2015-09-13T13:20:57Z","git_url":"git://github.com/QuantConnect/Lean.git","ssh_url":"git@github.com:QuantConnect/Lean.git","clone_url":"https://github.com/QuantConnect/Lean.git","svn_url":"https://github.com/QuantConnect/Lean","homepage":"https://lean.quantconnect.com","size":69713,"stargazers_count":267,"watchers_count":267,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":180,"mirror_url":null,"open_issues_count":35,"forks":180,"open_issues":35,"watchers":267,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/152"},"html":{"href":"https://github.com/QuantConnect/Lean/pull/152"},"issue":{"href":"https://api.github.com/repos/QuantConnect/Lean/issues/152"},"comments":{"href":"https://api.github.com/repos/QuantConnect/Lean/issues/152/comments"},"review_comments":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/152/comments"},"review_comment":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/QuantConnect/Lean/pulls/152/commits"},"statuses":{"href":"https://api.github.com/repos/QuantConnect/Lean/statuses/eef120901831d4bc2eb3a2cddc6b7b50732f4cca"}}}}