public async Task SaveTradeAggregates(IStorageTransaction transaction, ExchangeEnum exchange, SymbolCodeEnum symbolCode, ICollection <MarketTrade> trades)
        {
            var min = trades.Min(t => t.Epoch);
            var max = trades.Max(t => t.Epoch);

            var expanded = new List <TradeCartesian>();

            foreach (var group in IntervalFactory.ListGroups())
            {
                foreach (var ik in IntervalFactory.ListIntervalKeys(group.IntervalGroup))
                {
                    foreach (var trade in trades)
                    {
                        var period = IntervalFactory.GenerateIntervals(ik, trade.Epoch, trade.Epoch).FirstOrDefault();

                        expanded.Add(new TradeCartesian
                        {
                            IntervalKey   = period.IntervalKey,
                            IntervalEpoch = period.From,
                            Exchange      = trade.Exchange,
                            SymbolCode    = trade.SymbolCode,
                            Epoch         = trade.Epoch,
                            TradeId       = trade.TradeId,
                            Price         = trade.Price,
                            Volume        = trade.Volume,
                            Side          = trade.Side
                        });
                    }
                }
            }

            var context = (HistorianDbContext)transaction.GetContext();

            using (var cmd = context.Database.GetDbConnection().CreateCommand())
            {
                cmd.Transaction = context.Database.CurrentTransaction.GetDbTransaction();

                var sql = @"insert into `exchange_trade_aggregate`
                (`exchange_id`,
                `symbol_id`,
                `interval_key`,
                `timestamp`,
                `open`,
                `open_timestamp`,
                `high`,
                `low`,
                `close`,
                `close_timestamp`,
                `buy_volume`,
                `sell_volume`,
                `total_volume`,
                `buy_count`,
                `sell_count`,
                `total_count`) values ";

                sql += string.Join(",\r\n", expanded.Select(t => $"(" +
                                                            $"{(int)t.Exchange}," +
                                                            $"{(int)t.SymbolCode}," +
                                                            $"'{t.IntervalKey.Key}'," +
                                                            $"{t.IntervalEpoch.TimestampMilliseconds}," +
                                                            $"{t.Price}," +
                                                            $"{t.Epoch.TimestampMilliseconds}," +
                                                            $"{t.Price}," +
                                                            $"{t.Price}," +
                                                            $"{t.Price}," +
                                                            $"{t.Epoch.TimestampMilliseconds}," +
                                                            $"{(t.Side == OrderSideEnum.Buy ? t.Volume.ToString() : "NULL")}," +
                                                            $"{(t.Side == OrderSideEnum.Sell ? t.Volume.ToString() : "NULL")}," +
                                                            $"{t.Volume}," +
                                                            $"{(t.Side == OrderSideEnum.Buy ? "1" : "NULL")}," +
                                                            $"{(t.Side == OrderSideEnum.Sell ? "1" : "NULL")}," +
                                                            $"1)"));

                sql += @"
                on duplicate key update
	                `open` = case when values(`open_timestamp`) < `open_timestamp` then values(`open`) else `open` end,
	                `open_timestamp` = case when values(`open_timestamp`) < `open_timestamp` then values(`open_timestamp`) else `open_timestamp` end,
                    `high` = case when values(`high`) > `high` then values(`high`) else `high` end,
                    `low` = case when values(`low`) < `low` then values(`low`) else `low` end,
                    `close` = case when values(`close_timestamp`) > `close_timestamp` then values(`close`) else `close` end,
	                `close_timestamp` = case when values(`close_timestamp`) > `close_timestamp` then values(`close_timestamp`) else `close_timestamp` end,
	                `buy_volume` = case when values(`buy_volume`) is not null then ifnull(`buy_volume`, 0) + values(`buy_volume`) else `buy_volume` end,
                    `sell_volume` = case when values(`sell_volume`) is not null then ifnull(`sell_volume`, 0) + values(`sell_volume`) else `sell_volume` end,
                    `total_volume` = `total_volume` + values(`total_volume`),
                    `buy_count` = case when values(`buy_count`) is not null then ifnull(`buy_count`, 0) + values(`buy_count`) else `buy_count` end,
                    `sell_count` = case when values(`sell_count`) is not null then ifnull(`sell_count`, 0) + values(`sell_count`) else `sell_count` end,
                    `total_count` = `total_count` + values(`total_count`)";

                cmd.CommandText = sql;

                await cmd.ExecuteNonQueryAsync();
            }
        }
        public async Task SaveTradeStatAggregates(IStorageTransaction transaction, ExchangeEnum exchange, SymbolCodeEnum symbolCode, ICollection <MarketTradeStat> tradeStats)
        {
            var min = tradeStats.Min(t => t.Epoch);
            var max = tradeStats.Max(t => t.Epoch);

            var expanded = new List <TradeStatCartesian>();

            foreach (var group in IntervalFactory.ListGroups())
            {
                foreach (var ik in IntervalFactory.ListIntervalKeys(group.IntervalGroup))
                {
                    foreach (var tradeStat in tradeStats)
                    {
                        var period = IntervalFactory.GenerateIntervals(ik, tradeStat.Epoch, tradeStat.Epoch).FirstOrDefault();

                        expanded.Add(new TradeStatCartesian
                        {
                            IntervalKey   = period.IntervalKey,
                            IntervalEpoch = period.From,
                            Exchange      = tradeStat.Exchange,
                            SymbolCode    = symbolCode,
                            StatKey       = tradeStat.StatKey,
                            Epoch         = tradeStat.Epoch,
                            Value         = tradeStat.Value
                        });
                    }
                }
            }

            using (var context = ContextFactory.CreateDbContext(null))
            {
                using (var cmd = context.Database.GetDbConnection().CreateCommand())
                {
                    var sql = @"insert into `exchange_trade_aggregate`
                    (`exchange_id`,
                    `symbol_id`,
                    `interval_key`,
                    `timestamp`,
                    `open`,
                    `open_timestamp`,
                    `high`,
                    `low`,
                    `close`,
                    `close_timestamp`,
                    `total_count`) values ";

                    sql += string.Join(",\r\n", expanded.Select(t => $"(" +
                                                                $"{(int)t.Exchange}," +
                                                                $"{(int)t.SymbolCode}," +
                                                                $"'{t.IntervalKey.Key}'," +
                                                                $"{t.IntervalEpoch.TimestampMilliseconds}," +
                                                                $"{t.Value}," +
                                                                $"{t.Epoch.TimestampMilliseconds}," +
                                                                $"{t.Value}," +
                                                                $"{t.Value}," +
                                                                $"{t.Value}," +
                                                                $"{t.Epoch.TimestampMilliseconds}," +
                                                                $"1)"));

                    sql += @"
                    on duplicate key update
	                    `open` = case when values(`open_timestamp`) < `open_timestamp` then values(`open`) else `open` end,
	                    `open_timestamp` = case when values(`open_timestamp`) < `open_timestamp` then values(`open_timestamp`) else `open_timestamp` end,
                        `high` = case when values(`high`) > `high` then values(`high`) else `high` end,
                        `low` = case when values(`low`) < `low` then values(`low`) else `low` end,
                        `close` = case when values(`close_timestamp`) > `close_timestamp` then values(`close`) else `close` end,
	                    `close_timestamp` = case when values(`close_timestamp`) > `close_timestamp` then values(`close_timestamp`) else `close_timestamp` end,
	                    `total_count` = `total_count` + values(`total_count`)"    ;

                    cmd.CommandText = sql;

                    await cmd.Connection.OpenAsync();

                    await cmd.ExecuteNonQueryAsync();

                    cmd.Connection.Close();
                }
            }
        }
        public async Task <bool> Run(ILogger logger)
        {
            logger.LogInformation("Running repository bootstrapper");

            try
            {
                foreach (var currency in CurrencyFactory.List())
                {
                    await CurrencyRepository.Add(currency);
                }

                foreach (var symbol in SymbolFactory.List())
                {
                    await SymbolRepository.Add(symbol);
                }

                foreach (var group in IntervalFactory.ListGroups())
                {
                    foreach (var ik in IntervalFactory.ListIntervalKeys(group.IntervalGroup))
                    {
                        await IntervalRepository.Add(ik);
                    }
                }

                foreach (var exchange in ExchangeFactory.List())
                {
                    await ExchangeRepository.Add(exchange);

                    var httpClient = exchange.GetHttpClient();

                    foreach (var symbolCode in exchange.Symbol)
                    {
                        var symbol = SymbolFactory.Get(symbolCode);

                        await SymbolRepository.Add(symbol);

                        await ExchangeRepository.AddSymbol(exchange.Name, symbolCode);

                        var tradeFilter = await HistorianRepository.GetTradeFilter(exchange.Name, symbolCode);

                        if (tradeFilter == null)
                        {
                            using (var transaction = await StorageTransactionFactory.Begin())
                            {
                                await HistorianRepository.SetTradeFilter(transaction, exchange.Name, symbolCode, httpClient.InitialTradeFilter);

                                await transaction.Commit();
                            }
                        }
                    }
                }

                foreach (var orderSide in OrderSideFactory.List())
                {
                    await OrderSideRepository.Add(orderSide);
                }

                return(true);
            }
            catch (Exception ex)
            {
                logger.LogCritical(ex, "Unable to run repository bootstrapper");

                return(false);
            }
        }