private bool WaitTransactionsViaBrokerAndCollectExecutionInfo(BrokerTaskPortfolio p_portfolio, StringBuilder p_errorStr) { var transactions = p_portfolio.ProposedTransactions; if (transactions.Count == 0) return true; bool isSimulatedTrades = (bool)Trigger.TriggerSettings[BrokerTaskSetting.IsSimulatedTrades]; // simulation settings is too important to be forgotten to set. If it is not in settings, this will throw exception. OK. List<Task> tasks = new List<Task>(); for (int i = 0; i < transactions.Count; i++) { Task task = Task.Factory.StartNew((transaction) => { Controller.g_gatewaysWatcher.WaitOrder(p_portfolio.IbGatewayUserToTrade, ((Transaction)transaction).VirtualOrderId, isSimulatedTrades); }, transactions[i]); tasks.Add(task); } //Block until all transactions complete. Utils.Logger.Info("Before Task.WaitAllOrders(tasks)"); // http://stackoverflow.com/questions/9846615/async-task-whenall-with-timeout // Timeout here is not necessary, because BrokerWrapper.WaitOrder() will do a 2 minutes timeout for MKT orders, and Properly calculated MarketClose+2 minutes for MOC orders. // However, for absolute safety, to send Error Email to Supervisor, don't let an inifinte Task.WaitAll() here. // Imagine a case when MOC order was done at 9:30. Until 16:00, there is 6:30. So, do a 7hours timeout here. var allTasks = Task.WhenAll(tasks.ToArray()); var theFirstCompletedTask = Task.WhenAny(allTasks, Task.Delay(TimeSpan.FromHours(7))).Result; // returns false if timeout Utils.Logger.Info($"After Task.WaitAllOrders(tasks). Note: BrokerWrapper.WaitOrder() will do a 2 minutes timeout (MKT, MOC). Was it 7 hours timeout: {theFirstCompletedTask != allTasks} (Good, if there was no order timeout, but it is not a guarantee that everything was right. For example. Shares were not available for shorting."); bool wasAnyErrorInOrders = false; for (int i = 0; i < transactions.Count; i++) { var transaction = transactions[i]; OrderStatus orderStatus = OrderStatus.None; // a Property cannot be passed to a ref parameter, so we have to use temporary variables double executedVolume = Double.NaN; double executedAvgPrice = Double.NaN; DateTime executionTime = DateTime.UtcNow; if (!Controller.g_gatewaysWatcher.GetVirtualOrderExecutionInfo(p_portfolio.IbGatewayUserToTrade, transaction.VirtualOrderId, ref orderStatus, ref executedVolume, ref executedAvgPrice, ref executionTime, isSimulatedTrades)) { wasAnyErrorInOrders = true; p_errorStr.AppendLine($"GetVirtualOrderExecutionInfo() failed for virtualOrderId({transaction.VirtualOrderId}): {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume}."); } else { transaction.OrderStatus = orderStatus; transaction.ExecutedVolume = executedVolume; transaction.ExecutedAvgPrice = executedAvgPrice; transaction.DateTime = executionTime; if (transaction.OrderStatus == OrderStatus.MinFilterSkipped) { Utils.Logger.Info($"Ok. {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume}. Transaction.OrderStatus != OrderStatus.Filled. It is {transaction.OrderStatus}"); // This is Info. expected } else if (transaction.OrderStatus == OrderStatus.MaxFilterSkipped) { wasAnyErrorInOrders = true; p_errorStr.AppendLine($"Error. {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume}. Transaction.OrderStatus != OrderStatus.Filled. It is {transaction.OrderStatus}"); // This is Warn. not expected } else { if (Utils.IsNear(transaction.Volume, transaction.ExecutedVolume)) { // everything is OK. OrderStatus should be Filled or Partially Filled. if (transaction.OrderStatus != OrderStatus.Filled) { Utils.Logger.Warn($"{transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume}. Transaction.OrderStatus != OrderStatus.Filled. It is {transaction.OrderStatus}. Force it to be Filled."); transaction.OrderStatus = OrderStatus.Filled; } } else { wasAnyErrorInOrders = true; p_errorStr.AppendLine($"ERROR in {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume}, VirtualOrderId {transaction.VirtualOrderId}: transaction.OrderStatus != OrderStatus.Filled. It is {transaction.OrderStatus}"); // This is Error. not expected } // else } } // else } // for return !wasAnyErrorInOrders; } // WaitTransactionsViaBrokerAndCollectExecutionInfo()
private void PlaceTransactionsViaBroker(BrokerTaskPortfolio p_portfolio, StringBuilder p_detailedReportSb) { var transactions = p_portfolio.ProposedTransactions; if (transactions.Count == 0) { Utils.ConsoleWriteLine(ConsoleColor.Green, false, $"***Trades: none."); Utils.Logger.Info($"***Trades: none."); p_detailedReportSb.AppendLine($"<font color=\"#10ff10\">***Trades: none.</font>"); return; } // OPG order: tried in IB: puting 2 minutes before open: result: invalid order (after 10 sec); 3 minutes before open: it was successfull // in theory: "Market on open orders must be placed at least 20 minutes", but IB doesn't specify the time. // apparently ,the exchanges close the time window 2 minutes before market open. So, if IB is quick enough, it can accept trades even in the last 5 minutes (once I tried with 3 minutes manually, and was successfull) // so: with IB: try: 5 minutes, and increase it 1 minute every time it failed. (at the end, we will get to 20 minutes). It may depend on the stock exchange. There are more lenient stock exchanges // An MOC (Market on Close) order must be submitted no later than 15 minutes prior to the close of the market. object orderExecutionObj = BrokerTaskSchema.Settings[BrokerTaskSetting.OrderExecution]; // "MKT", "LMT", "MOC" OrderExecution orderExecution = (orderExecutionObj != null) ? (OrderExecution)orderExecutionObj : OrderExecution.Market; // Market is the default object orderTifObj = null; OrderTimeInForce orderTif = (BrokerTaskSchema.Settings.TryGetValue(BrokerTaskSetting.OrderTimeInForce, out orderTifObj)) ? (OrderTimeInForce)orderTifObj : OrderTimeInForce.Day; // Day is the default StrongAssert.True(orderExecution == OrderExecution.Market || orderExecution == OrderExecution.MarketOnClose, Severity.ThrowException, $"Non supported OrderExecution: {orderExecution}"); StrongAssert.True(orderTif == OrderTimeInForce.Day, Severity.ThrowException, $"Non supported OrderTimeInForce: {orderTif}"); bool isSimulatedTrades = (bool)Trigger.TriggerSettings[BrokerTaskSetting.IsSimulatedTrades]; // simulation settings is too important to be forgotten to set. If it is not in settings, this will throw exception. OK. for (int i = 0; i < transactions.Count; i++) // quickly place the orders. Don't do any other time consuming work here. { var transaction = transactions[i]; Utils.Logger.Info($"Placing Order {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume} "); Contract contract = new Contract() { Symbol = Strategy.StockIdToTicker(transaction.SubTableID), SecType = "STK", Currency = "USD", Exchange = "SMART" }; transaction.VirtualOrderId = Controller.g_gatewaysWatcher.PlaceOrder(p_portfolio.IbGatewayUserToTrade, p_portfolio.MaxTradeValueInCurrency, p_portfolio.MinTradeValueInCurrency, contract, transaction.TransactionType, transaction.Volume, orderExecution, orderTif, null, null, isSimulatedTrades, p_detailedReportSb); } // don't do anything here. Return, so other portfolio PlaceOrder()-s can be executed too. }
private void DetermineProposedPositions(BrokerTaskPortfolio p_portfolio, List<PortfolioPositionSpec> p_proposedPositionSpecs) { double leverage = Strategy.GetPortfolioLeverage(p_proposedPositionSpecs, p_portfolio.Param); double totalRiskedCapital = p_portfolio.PortfolioUsdSize * leverage; var rtPrices = new Dictionary<int, PriceAndTime>() { { TickType.MID, new PriceAndTime() } }; // MID is the most honest price. LAST may happened 1 hours ago p_portfolio.ProposedPositions = p_proposedPositionSpecs.Select(r => { int volume = 0; if (r.Size is FixedSize) volume = (int)(r.Size as FixedSize).Size * (r.IsShort ? -1 : 1); else { Contract contract = new Contract() { Symbol = r.Ticker, SecType = "STK", Currency = "USD", Exchange = "SMART" }; double rtPrice = 0.0; StrongAssert.True(Controller.g_gatewaysWatcher.GetMktDataSnapshot(contract, ref rtPrices), Severity.ThrowException, $"Warning. Realtime price for {r.Ticker} is misssing. For safety reasons, we can use volume=0 as targetVolume, but it means we would sell the current positions. Better to not continue."); rtPrice = rtPrices[TickType.MID].Price; volume = (int)((r.Size as WeightedSize).Weight * totalRiskedCapital / rtPrice) * (r.IsShort ? -1 : 1); } return new PortfolioPosition() { AssetID = Strategy.TickerToAssetID(r.Ticker), Volume = volume }; }).ToList(); foreach (var suggestedItem in p_portfolio.ProposedPositions) { Utils.ConsoleWriteLine(null, false, $"Portfolio suggestion: {Strategy.StockIdToTicker(suggestedItem.SubTableID)}: signed vol: {suggestedItem.Volume}"); Utils.Logger.Info($"Portfolio suggestion: {Strategy.StockIdToTicker(suggestedItem.SubTableID)}: signed vol: {suggestedItem.Volume}"); } }
private void DetermineProposedTransactions(BrokerTaskPortfolio p_portfolio) { List<Transaction> transactions = new List<Transaction>(); // 1. close old positions //var closedPositions = p_previousPidsWithoutCash.Where(r => !p_targetPortfolio.Select(q => q.AssetID).Contains(r.AssetID)); foreach (var todayPos in p_portfolio.TodayPositions) { if (todayPos.AssetTypeID == AssetType.HardCash) continue; // skip cash positions //Utils.Logger.Warn($"Prev Item {Strategy.StockIdToTicker(todayPos.SubTableID)}: {todayPos.Volume} "); var proposed = p_portfolio.ProposedPositions.FirstOrDefault(r => r.AssetID == todayPos.AssetID); if (proposed == null) // if an assetID is only in todayPos, it is here { transactions.Add(new Transaction() { AssetID = todayPos.AssetID, Volume = Math.Abs(todayPos.Volume), // Volume should be always positive TransactionType = (todayPos.Volume > 0) ? TransactionType.SellAsset : TransactionType.BuyAsset // it was Cover instead of Buy, But we decided to simplify and allow Buy }); } else { // if an assetID is both in todayPos and in proposedPos, it is here double diffVolume = proposed.Volume - todayPos.Volume; if (!Utils.IsNearZero(diffVolume)) // don't insert transaction, if volume will be 0 { transactions.Add(new Transaction() { AssetID = todayPos.AssetID, Volume = Math.Abs(diffVolume), // Volume should be always positive TransactionType = (diffVolume > 0) ? TransactionType.BuyAsset : TransactionType.SellAsset }); } } } // 2. open new positions: foreach (var proposedPos in p_portfolio.ProposedPositions) { //Utils.Logger.Warn($"New Item {Strategy.StockIdToTicker(proposedPos.SubTableID)}: {proposedPos.Volume} "); var todayPos = p_portfolio.TodayPositions.FirstOrDefault(r => r.AssetID == proposedPos.AssetID); if (todayPos == null) // // if an assetID is only in proposedPos, it is here { transactions.Add(new Transaction() { AssetID = proposedPos.AssetID, Volume = Math.Abs(proposedPos.Volume), // Volume should be always positive TransactionType = (proposedPos.Volume > 0) ? TransactionType.BuyAsset : TransactionType.SellAsset // it was Cover instead of Buy, But we decided to simplify and allow Buy }); } } foreach (var transaction in transactions) { Utils.ConsoleWriteLine(ConsoleColor.Green, false, $"***Proposed transaction: {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume} "); Utils.Logger.Info($"***Proposed transaction: {transaction.TransactionType} {Strategy.StockIdToTicker(transaction.SubTableID)}: {transaction.Volume} "); } p_portfolio.ProposedTransactions = transactions; // only assign at the end, if everything was right, there was no thrown Exception. It is safer to do transactions: all or nothing, not partial }
public void CalculatePortfolioUsdSizeFromRealTime(BrokerTaskPortfolio p_portfolio) { var rtPrices = new Dictionary<int, PriceAndTime>() { { TickType.MID, new PriceAndTime() } }; // MID is the most honest price. LAST may happened 1 hours ago double portfolioUsdSize = 0; foreach (PortfolioPosition pip in p_portfolio.TodayPositions) { if (pip.AssetTypeID == AssetType.HardCash) { if ((CurrencyID)pip.SubTableID == CurrencyID.USD) portfolioUsdSize += pip.Volume; // for Cash, the Volume = 1 in the SQL DB, but we store the Value in the Volume, not in the Price, because of possible future other currencies else throw new Exception("Only USD is implemented"); // in the future may use fixed or estimated or real time Forex prices } else { int stockID = pip.SubTableID; // TODO: we need a tickerProvider here Contract contract = new Contract() { Symbol = Strategy.StockIdToTicker(stockID), SecType = "STK", Currency = "USD", Exchange = "SMART" }; double rtPrice = 0.0; StrongAssert.True(Controller.g_gatewaysWatcher.GetMktDataSnapshot(contract, ref rtPrices), Severity.ThrowException, "There is no point continuing if portfolioUSdSize cannot be calculated. After that we cannot calculate new stock Volumes from weights."); rtPrice = rtPrices[TickType.MID].Price; //double rtPrice = GetAssetIDRealTimePrice(BrokerTask.TaskLogFile, p_brokerAPI, pip.AssetID); portfolioUsdSize += pip.Volume * rtPrice; // pip.Volume is signed. For shorts, it is negative, but that is OK. } } p_portfolio.PortfolioUsdSize = portfolioUsdSize; Utils.ConsoleWriteLine(null, false, $"Portfolio ({p_portfolio.PortfolioID}) $size (realtime): ${p_portfolio.PortfolioUsdSize:F2}"); Utils.Logger.Info($"!!!Portfolio ({p_portfolio.PortfolioID}) $size (realtime): ${p_portfolio.PortfolioUsdSize:F2}"); }