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);
        }