public StockHoldingDecesionResult MakeStockHoldingDecision(string azureTableStockCode, bool realTime, ITradePolicy policy, bool savePlannedDecesionBuyPrice = false, double checkBestBuyChanceDelta = 0.02) { CloudTable table = GetAzureTable(); TableOperation retrieveStockEntityStatus = TableOperation.Retrieve<StockEntityStatus>("status-" + azureTableStockCode, "status"); var stockEntityStatus = (StockEntityStatus)table.Execute(retrieveStockEntityStatus).Result; if (stockEntityStatus == null) { throw new InvalidOperationException("Please upload data and do initial processing first."); } DateTime stockHistoryFetchStartTime = DateTime.Now.Subtract(TimeSpan.FromDays(60)); if (stockEntityStatus.LastBoughtDate < stockHistoryFetchStartTime) stockHistoryFetchStartTime = stockEntityStatus.LastBoughtDate; if (stockEntityStatus.LastSoldDate < stockHistoryFetchStartTime) stockHistoryFetchStartTime = stockEntityStatus.LastSoldDate; string pkFilter = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, azureTableStockCode); string rkFilter = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, stockHistoryFetchStartTime.ToString("yyyy-MM-dd")); string combinedFilter = TableQuery.CombineFilters(pkFilter, TableOperators.And, rkFilter); TableQuery<StockEntity> query = new TableQuery<StockEntity>().Where(combinedFilter); var sortedStockEntities = table.ExecuteQuery<StockEntity>(query).OrderBy(entity => entity.Date).ToList(); // TODO: check data completeness StockEntity stockEntity; if (!realTime) { stockEntity = sortedStockEntities.Last<StockEntity>(); } else { stockEntity = dataSourceClient.GetRealtimeStockData(azureTableStockCode); sortedStockEntities.Add(stockEntity); } var lastBoughtStockEntity = sortedStockEntities.FindLast(entity => entity.RowKey == stockEntityStatus.LastBoughtDate.ToString("yyyy-MM-dd")); var lastSoldStockEntity = sortedStockEntities.FindLast(entity => entity.RowKey == stockEntityStatus.LastSoldDate.ToString("yyyy-MM-dd")); StockHoldingDecesionResult result = null; bool buyStock = policy.BuyPolicy(stockEntity, lastBoughtStockEntity, lastSoldStockEntity, sortedStockEntities); bool sellStock = policy.SellPolicy(stockEntity, lastBoughtStockEntity, lastSoldStockEntity, sortedStockEntities); if (stockEntityStatus.StockHoldingStatus) { if (sellStock) { result = new StockHoldingDecesionResult { Price = stockEntity.Close, Decesion = StockHoldingDecesion.Sell, LastRawDataDate = stockEntityStatus.LastestRawDataDate }; } else { result = new StockHoldingDecesionResult { Price = stockEntity.Close, Decesion = StockHoldingDecesion.Hold, LastRawDataDate = stockEntityStatus.LastestRawDataDate }; } } else { if (buyStock) { if (stockEntityStatus.PlannedDecesionBuyPrice <= 0 || stockEntity.Close < stockEntityStatus.PlannedDecesionBuyPrice * (1 + checkBestBuyChanceDelta)) { result = new StockHoldingDecesionResult { Price = stockEntity.Close, Decesion = StockHoldingDecesion.Buy, LastRawDataDate = stockEntityStatus.LastestRawDataDate }; } else { result = new StockHoldingDecesionResult { Price = stockEntity.Close, Decesion = StockHoldingDecesion.MissBestBuyChance, LastRawDataDate = stockEntityStatus.LastestRawDataDate }; } } else { result = new StockHoldingDecesionResult { Price = stockEntity.Close, Decesion = StockHoldingDecesion.Empty, LastRawDataDate = stockEntityStatus.LastestRawDataDate }; } } if (savePlannedDecesionBuyPrice) { if (buyStock && stockEntityStatus.PlannedDecesionBuyPrice <= 0 && stockEntity.Close > 0) { Console.WriteLine("Set {0} planned decesion buy price to {1}", azureTableStockCode, stockEntity.Close); stockEntityStatus.PlannedDecesionBuyPrice = stockEntity.Close; table.Execute(TableOperation.InsertOrMerge(stockEntityStatus)); } else if (sellStock && stockEntityStatus.PlannedDecesionBuyPrice >= 0) { Console.WriteLine("Set {0} planned decesion buy price to -1", azureTableStockCode); stockEntityStatus.PlannedDecesionBuyPrice = -1; table.Execute(TableOperation.InsertOrMerge(stockEntityStatus)); } } return result; }
public double CheckWinRate(string azureTableStockCode, ITradePolicy policy, string dateTimeSince = "1990-01-01", bool noDividend = false, bool saveResult = false) { CloudTable table = GetAzureTable(); string pkFilter = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, azureTableStockCode); string rkFilter = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, dateTimeSince); string combinedFilter = TableQuery.CombineFilters(pkFilter, TableOperators.And, rkFilter); TableQuery<StockEntity> query = new TableQuery<StockEntity>().Where(combinedFilter); var sortedStockEntities = table.ExecuteQuery<StockEntity>(query).OrderBy(entity => entity.Date).ToList(); double money = 1000; double quantity = -1; StockEntity lastBoughtStockEntity = new StockEntity(); StockEntity lastSoldStockEntity = new StockEntity(); StockEntity yesterdayStockEntity = null; long winCount = 0; long hugeWinCount = 0; long loseCount = 0; long hugeLoseCount = 0; foreach (var stockEntity in sortedStockEntities) { if (quantity < 0) { if (policy.BuyPolicy(stockEntity, lastBoughtStockEntity, lastSoldStockEntity, sortedStockEntities)) { lastBoughtStockEntity = BuyStock(stockEntity, ref money, ref quantity); } } else if (quantity > 0) { bool bonusDividend = false; double devidendRate = 1; if (!noDividend) { if (yesterdayStockEntity != null && stockEntity.Open > 0 && stockEntity.Open < yesterdayStockEntity.Close * 0.9) { Console.WriteLine("It's impossible that much lose happened, bonus dividend must have happened!"); bonusDividend = true; devidendRate = yesterdayStockEntity.Close / stockEntity.Open; quantity *= devidendRate; } } if (policy.SellPolicy(stockEntity, lastBoughtStockEntity, lastSoldStockEntity, sortedStockEntities)) { lastSoldStockEntity = SellStock(stockEntity, ref money, ref quantity); if (stockEntity.Close * devidendRate > lastBoughtStockEntity.Close) { Console.WriteLine("{0}: Win {1}%", stockEntity.Date.ToString("yyyy-MM-dd"), (stockEntity.Close * devidendRate - lastBoughtStockEntity.Close) / lastBoughtStockEntity.Close * 100); winCount++; if (stockEntity.Close * devidendRate > lastBoughtStockEntity.Close * 1.1) { hugeWinCount++; } } else { Console.WriteLine("{0}: Lose {1}%", stockEntity.Date.ToString("yyyy-MM-dd"), (lastBoughtStockEntity.Close - stockEntity.Close * devidendRate) / lastBoughtStockEntity.Close * 100); loseCount++; if (!bonusDividend && stockEntity.Close < lastBoughtStockEntity.Close * 0.9) { hugeLoseCount++; } } } } yesterdayStockEntity = stockEntity; } if (quantity > 0 && sortedStockEntities.LongCount() > 0) { money = sortedStockEntities.Last().Close * quantity; } Console.WriteLine("{4}: Money at the end is {0}, win count = {1}, huge win count = {5}, lose count = {2}, huge lose count = {6}, win rate = {3}%", money, winCount, loseCount, (double)winCount * 100 / (winCount + loseCount), azureTableStockCode, hugeWinCount, hugeLoseCount); if (saveResult) { TableOperation retrieveStockEntityStatus = TableOperation.Retrieve<StockEntityStatus>("status-" + azureTableStockCode, "status"); var stockEntityStatus = (StockEntityStatus)table.Execute(retrieveStockEntityStatus).Result; if (stockEntityStatus == null) { throw new InvalidOperationException("Please upload data and do initial processing first."); } if (dateTimeSince == "1990-01-01") { stockEntityStatus.OverallWinRate = (double)winCount * 100 / (winCount + loseCount); stockEntityStatus.OverallProfitRate = (money - 1000) * 100 / 1000; } else { stockEntityStatus.RecentWinRate = (double)winCount * 100 / (winCount + loseCount); stockEntityStatus.RecentProfitRate = (money - 1000) * 100 / 1000; } table.Execute(TableOperation.InsertOrMerge(stockEntityStatus)); } return (double)winCount * 100 / (winCount + loseCount); }