diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ba15d4f..a61f0e4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,10 @@ jobs: fail-fast: true matrix: os: [ "ubuntu-latest", "macos-latest" ] - python-version: [ "3.8", "3.9", "3.10", "3.11" ] + python-version: [ "3.9", "3.10", "3.11" ] + defaults: + run: + shell: bash runs-on: ${{ matrix.os }} steps: #---------------------------------------------- @@ -99,7 +102,7 @@ jobs: #---------------------------------------------- - name: Run tests run: | - source .venv/bin/activate + source $VENV coverage run -m unittest discover -s tests # #---------------------------------------------- # # upload coverage stats diff --git a/README.md b/README.md index e3a987dd..f2ee2a15 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ Features: The following algorithm connects to binance and buys BTC every 5 seconds. It also exposes an REST API that allows you to interact with the algorithm. ```python -import logging import logging.config from investing_algorithm_framework import create_app, PortfolioConfiguration, \ @@ -61,7 +60,6 @@ bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( symbol="BTC/EUR", ) app = create_app() -algorithm = Algorithm() # Bitvavo market credentials are read from .env file app.add_market_credential(MarketCredential(market="bitvavo")) app.add_portfolio_configuration( @@ -71,10 +69,9 @@ app.add_portfolio_configuration( initial_balance=400 ) ) -app.add_algorithm(algorithm) # Run every two hours and register the data sources -@algorithm.strategy( +@app.strategy( time_unit=TimeUnit.HOUR, interval=2, market_data_sources=[bitvavo_btc_eur_ticker, bitvavo_btc_eur_ohlcv_2h] @@ -82,7 +79,6 @@ app.add_algorithm(algorithm) def perform_strategy(algorithm: Algorithm, market_data: dict): # Access the data sources with the indentifier polars_df = market_data["BTC-ohlcv"] - # Convert the polars dataframe to a pandas dataframe pandas_df = polars_df.to_pandas() ticker_data = market_data["BTC-ticker"] @@ -93,18 +89,21 @@ def perform_strategy(algorithm: Algorithm, market_data: dict): closed_trades = algorithm.get_closed_trades() # Create a buy oder - algorithm.create_limit_order( + order = algorithm.create_limit_order( target_symbol="BTC/EUR", order_side="buy", amount=0.01, price=ticker_data["ask"], ) + trade = algorithm.get_trade(order_id=order.id) + algorithm.add_trailing_stop_loss(trade=trade, percentage=5) # Close a trade - algorithm.close_trade(trades[0].id) + algorithm.close_trade(trade=trade) # Close a position - algorithm.close_position(positions[0].get_symbol()) + position = algorithm.get_position(symbol="BTC/EUR") + algorithm.close_position(position) if __name__ == "__main__": app.run() @@ -116,7 +115,14 @@ if __name__ == "__main__": The framework also supports backtesting and performing backtest experiments. After a backtest, you can print a report that shows the performance of your trading bot. -To run a single backtest you can use the example code that can be found [here](./examples/backtest). +To run a single backtest you can use the example code that can be found [here](./examples/backtest_example). Simply run: + +> Its assumed here that you have cloned the repository, installed the framework and +> are in the root of the project. + +```bash +python examples/backtest_example/run_backtest.py +``` ### Backtesting report diff --git a/examples/app.py b/examples/app.py deleted file mode 100644 index ee2d1cbd..00000000 --- a/examples/app.py +++ /dev/null @@ -1,49 +0,0 @@ -from dotenv import load_dotenv - -from investing_algorithm_framework import create_app, PortfolioConfiguration, \ - TimeUnit, CCXTOHLCVMarketDataSource, Algorithm, \ - CCXTTickerMarketDataSource, MarketCredential, AzureBlobStorageStateHandler - -load_dotenv() - -# Define market data sources -# OHLCV data for candles -bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( - identifier="BTC-ohlcv", - market="BITVAVO", - symbol="BTC/EUR", - time_frame="2h", - window_size=200 -) -# Ticker data for orders, trades and positions -bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( - identifier="BTC-ticker", - market="BITVAVO", - symbol="BTC/EUR", -) -app = create_app(state_handler=AzureBlobStorageStateHandler()) -app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h) -algorithm = Algorithm() -app.add_market_credential(MarketCredential(market="bitvavo")) -app.add_portfolio_configuration( - PortfolioConfiguration( - market="bitvavo", - trading_symbol="EUR", - initial_balance=20 - ) -) -app.add_algorithm(algorithm) - -@algorithm.strategy( - # Run every two hours - time_unit=TimeUnit.HOUR, - interval=2, - # Specify market data sources that need to be passed to the strategy - market_data_sources=[bitvavo_btc_eur_ticker, "BTC-ohlcv"] -) -def perform_strategy(algorithm: Algorithm, market_data: dict): - # By default, ohlcv data is passed as polars df in the form of - # {"": } https://pola.rs/, - # call to_pandas() to convert to pandas - polars_df = market_data["BTC-ohlcv"] - print(f"I have access to {len(polars_df)} candles of ohlcv data") diff --git a/examples/backtest_example/__init__.py b/examples/backtest_example/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/backtest_example/algorithm/__init__.py b/examples/backtest_example/algorithm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/backtest_example/algorithm/algorithm.py b/examples/backtest_example/algorithm/algorithm.py deleted file mode 100644 index 6c9caa3c..00000000 --- a/examples/backtest_example/algorithm/algorithm.py +++ /dev/null @@ -1,5 +0,0 @@ -from investing_algorithm_framework import Algorithm -from .strategy import CrossOverStrategy - -algorithm = Algorithm() -algorithm.add_strategy(CrossOverStrategy) diff --git a/examples/backtest_example/algorithm/data_sources.py b/examples/backtest_example/algorithm/data_sources.py deleted file mode 100644 index 04243040..00000000 --- a/examples/backtest_example/algorithm/data_sources.py +++ /dev/null @@ -1,30 +0,0 @@ -from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \ - CCXTTickerMarketDataSource - - -bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( - identifier="BTC/EUR-ohlcv", - market="BINANCE", - symbol="BTC/EUR", - timeframe="2h", - window_size=200 -) -bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( - identifier="DOT/EUR-ohlcv", - market="BINANCE", - symbol="DOT/EUR", - timeframe="2h", - window_size=200 -) -bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( - identifier="DOT/EUR-ticker", - market="BINANCE", - symbol="DOT/EUR", - backtest_timeframe="2h", -) -bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( - identifier="BTC/EUR-ticker", - market="BINANCE", - symbol="BTC/EUR", - backtest_timeframe="2h", -) diff --git a/examples/backtest_example/algorithm/strategy.py b/examples/backtest_example/algorithm/strategy.py deleted file mode 100644 index d98c6500..00000000 --- a/examples/backtest_example/algorithm/strategy.py +++ /dev/null @@ -1,108 +0,0 @@ -import tulipy as ti - -from investing_algorithm_framework import TimeUnit, TradingStrategy, \ - Algorithm, OrderSide -from .data_sources import bitvavo_btc_eur_ohlcv_2h, bitvavo_btc_eur_ticker, \ - bitvavo_dot_eur_ticker, bitvavo_dot_eur_ohlcv_2h - -""" -This strategy is based on the golden cross strategy. It will buy when the -fast moving average crosses the slow moving average from below. It will sell -when the fast moving average crosses the slow moving average from above. -The strategy will also check if the fast moving average is above the trend -moving average. If it is not above the trend moving average it will not buy. - -It uses tulipy indicators to calculate the metrics. You need to -install this library in your environment to run this strategy. -You can find instructions on how to install tulipy here: -https://tulipindicators.org/ or go directly to the pypi page: -https://pypi.org/project/tulipy/ -""" - - -def is_below_trend(fast_series, slow_series): - return fast_series[-1] < slow_series[-1] - - -def is_above_trend(fast_series, slow_series): - return fast_series[-1] > slow_series[-1] - - -def is_crossover(fast, slow): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return fast[-2] <= slow[-2] and fast[-1] > slow[-1] - - -def is_crossunder(fast, slow): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return fast[-2] >= slow[-2] and fast[-1] < slow[-1] - - -class CrossOverStrategy(TradingStrategy): - time_unit = TimeUnit.HOUR - interval = 2 - market_data_sources = [ - bitvavo_dot_eur_ticker, - bitvavo_btc_eur_ticker, - bitvavo_dot_eur_ohlcv_2h, - bitvavo_btc_eur_ohlcv_2h - ] - symbols = ["BTC/EUR", "DOT/EUR"] - fast = 21 - slow = 75 - trend = 150 - stop_loss_percentage = 7 - - def apply_strategy(self, algorithm: Algorithm, market_data): - - for symbol in self.symbols: - target_symbol = symbol.split('/')[0] - - if algorithm.has_open_orders(target_symbol): - continue - - df = market_data[f"{symbol}-ohlcv"] - ticker_data = market_data[f"{symbol}-ticker"] - fast = ti.sma(df['Close'].to_numpy(), self.fast) - slow = ti.sma(df['Close'].to_numpy(), self.slow) - trend = ti.sma(df['Close'].to_numpy(), self.trend) - price = ticker_data["bid"] - - if not algorithm.has_position(target_symbol) \ - and is_crossover(fast, slow) \ - and is_above_trend(fast, trend): - algorithm.create_limit_order( - target_symbol=target_symbol, - order_side=OrderSide.BUY, - price=price, - percentage_of_portfolio=25, - precision=4, - ) - - if algorithm.has_position(target_symbol) \ - and is_below_trend(fast, slow): - open_trades = algorithm.get_open_trades( - target_symbol=target_symbol - ) - - for trade in open_trades: - algorithm.close_trade(trade) - - # Checking manual stop losses - open_trades = algorithm.get_open_trades(target_symbol) - - for open_trade in open_trades: - if open_trade.is_manual_stop_loss_trigger( - ohlcv_df=df, - current_price=market_data[f"{symbol}-ticker"]["bid"], - stop_loss_percentage=self.stop_loss_percentage - ): - algorithm.close_trade(open_trade) diff --git a/examples/backtest_example/app.py b/examples/backtest_example/app.py deleted file mode 100644 index 2214545b..00000000 --- a/examples/backtest_example/app.py +++ /dev/null @@ -1,11 +0,0 @@ -import pathlib - -from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY - -app = create_app( - config={RESOURCE_DIRECTORY: pathlib.Path(__file__).parent.resolve()} -) - - -if __name__ == "__main__": - app.run() diff --git a/examples/backtest_example/run_backtest.py b/examples/backtest_example/run_backtest.py index 1d1f5cb8..67edff1d 100644 --- a/examples/backtest_example/run_backtest.py +++ b/examples/backtest_example/run_backtest.py @@ -1,19 +1,150 @@ +import logging.config from datetime import datetime, timedelta -from algorithm.algorithm import algorithm -from algorithm.data_sources import bitvavo_btc_eur_ohlcv_2h, \ - bitvavo_dot_eur_ohlcv_2h, bitvavo_dot_eur_ticker, bitvavo_btc_eur_ticker -from app import app -from investing_algorithm_framework import PortfolioConfiguration, \ - pretty_print_backtest, BacktestDateRange +from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \ + CCXTTickerMarketDataSource, Algorithm, PortfolioConfiguration, \ + create_app, pretty_print_backtest, BacktestDateRange, TimeUnit, \ + TradingStrategy, OrderSide, DEFAULT_LOGGING_CONFIG + +import tulipy as ti + +logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) + + +""" +This strategy is based on the golden cross strategy. It will buy when the +fast moving average crosses the slow moving average from below. It will sell +when the fast moving average crosses the slow moving average from above. +The strategy will also check if the fast moving average is above the trend +moving average. If it is not above the trend moving average it will not buy. + +It uses tulipy indicators to calculate the metrics. You need to +install this library in your environment to run this strategy. +You can find instructions on how to install tulipy here: +https://tulipindicators.org/ or go directly to the pypi page: +https://pypi.org/project/tulipy/ +""" + +bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( + identifier="BTC/EUR-ohlcv", + market="BINANCE", + symbol="BTC/EUR", + time_frame="2h", + window_size=200 +) +bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( + identifier="DOT/EUR-ohlcv", + market="BINANCE", + symbol="DOT/EUR", + time_frame="2h", + window_size=200 +) +bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( + identifier="DOT/EUR-ticker", + market="BINANCE", + symbol="DOT/EUR", + backtest_time_frame="2h", +) +bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( + identifier="BTC/EUR-ticker", + market="BINANCE", + symbol="BTC/EUR", + backtest_time_frame="2h", +) + + +def is_below_trend(fast_series, slow_series): + return fast_series[-1] < slow_series[-1] + + +def is_above_trend(fast_series, slow_series): + return fast_series[-1] > slow_series[-1] + + +def is_crossover(fast, slow): + """ + Expect df to have columns: Date, ma_, ma_. + With the given date time it will check if the ma_ is a + crossover with the ma_ + """ + return fast[-2] <= slow[-2] and fast[-1] > slow[-1] + + +def is_crossunder(fast, slow): + """ + Expect df to have columns: Date, ma_, ma_. + With the given date time it will check if the ma_ is a + crossover with the ma_ + """ + return fast[-2] >= slow[-2] and fast[-1] < slow[-1] + + +class CrossOverStrategy(TradingStrategy): + time_unit = TimeUnit.HOUR + interval = 2 + market_data_sources = [ + bitvavo_dot_eur_ticker, + bitvavo_btc_eur_ticker, + bitvavo_dot_eur_ohlcv_2h, + bitvavo_btc_eur_ohlcv_2h + ] + symbols = ["BTC/EUR", "DOT/EUR"] + fast = 21 + slow = 75 + trend = 150 + stop_loss_percentage = 7 + + def apply_strategy(self, algorithm: Algorithm, market_data): + + for symbol in self.symbols: + target_symbol = symbol.split('/')[0] + + if algorithm.has_open_orders(target_symbol): + continue + + df = market_data[f"{symbol}-ohlcv"] + ticker_data = market_data[f"{symbol}-ticker"] + fast = ti.sma(df['Close'].to_numpy(), self.fast) + slow = ti.sma(df['Close'].to_numpy(), self.slow) + trend = ti.sma(df['Close'].to_numpy(), self.trend) + price = ticker_data["bid"] + + if not algorithm.has_position(target_symbol) \ + and is_crossover(fast, slow) \ + and is_above_trend(fast, trend): + order = algorithm.create_limit_order( + target_symbol=target_symbol, + order_side=OrderSide.BUY, + price=price, + percentage_of_portfolio=25, + precision=4, + ) + trade = algorithm.get_trade(order_id=order.id) + algorithm.add_trailing_stop_loss( + trade=trade, percentage=5 + ) + + + if algorithm.has_position(target_symbol) \ + and is_below_trend(fast, slow): + open_trades = algorithm.get_open_trades( + target_symbol=target_symbol + ) + + for trade in open_trades: + algorithm.close_trade(trade) + + +app = create_app() +algorithm = Algorithm("GoldenCrossStrategy") +algorithm.add_strategy(CrossOverStrategy) app.add_algorithm(algorithm) app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h) app.add_market_data_source(bitvavo_dot_eur_ohlcv_2h) app.add_market_data_source(bitvavo_btc_eur_ticker) app.add_market_data_source(bitvavo_dot_eur_ticker) - # Add a portfolio configuration of 400 euro initial balance app.add_portfolio_configuration( PortfolioConfiguration( diff --git a/examples/backtests_example/__init__.py b/examples/backtests_example/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/backtests_example/algorithms/__init__.py b/examples/backtests_example/algorithms/__init__.py deleted file mode 100644 index d6a1b27d..00000000 --- a/examples/backtests_example/algorithms/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .challenger import create_algorithm as create_challenger_algorithm -from .primary import create_algorithm as create_primary_algorithm - - -__all__ = ["create_challenger_algorithm", "create_primary_algorithm"] diff --git a/examples/backtests_example/algorithms/challenger.py b/examples/backtests_example/algorithms/challenger.py deleted file mode 100644 index 70a86a9d..00000000 --- a/examples/backtests_example/algorithms/challenger.py +++ /dev/null @@ -1,148 +0,0 @@ -import tulipy as tp -import numpy as np - -from investing_algorithm_framework import Algorithm, TradingStrategy, \ - TimeUnit, OrderSide, CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource - - -btc_eur_ohlcv_2h_data = CCXTOHLCVMarketDataSource( - identifier="BTC/EUR_ohlcv_2h", - symbol="BTC/EUR", - market="BITVAVO", - timeframe="2h", - window_size=200, -) - -btc_eur_ticker_data = CCXTTickerMarketDataSource( - identifier="BTC/EUR_ticker", - symbol="BTC/EUR", - market="BITVAVO", - backtest_timeframe="2h" -) - - -def is_below_trend(fast_series, slow_series): - return fast_series[-1] < slow_series[-1] - - -def is_above_trend(fast_series, slow_series): - return fast_series[-1] > slow_series[-1] - - -class Strategy(TradingStrategy): - time_unit = TimeUnit.HOUR - interval = 2 - market_data_sources = [ - btc_eur_ohlcv_2h_data, - btc_eur_ticker_data - ] - symbols = ["BTC/EUR"] - - def __init__( - self, - short_period, - long_period, - rsi_period, - rsi_buy_threshold, - rsi_sell_threshold - ): - self.fast = short_period - self.slow = long_period - self.rsi_period = rsi_period - self.rsi_buy_threshold = rsi_buy_threshold - self.rsi_sell_threshold = rsi_sell_threshold - super().__init__() - - def apply_strategy(self, algorithm: Algorithm, market_data): - - for symbol in self.symbols: - target_symbol = symbol.split('/')[0] - - if algorithm.has_open_orders(target_symbol): - continue - - df = market_data[f"{symbol}_ohlcv_2h"].to_pandas() - df = self.add_ema(df, "Close", self.fast) - df = self.add_ema(df, "Close", self.slow) - df = self.add_rsi(df, self.rsi_period) - ticker_data = market_data[f"{symbol}_ticker"] - price = ticker_data['bid'] - - if not algorithm.has_position(target_symbol) \ - and self.is_crossover( - df, f"EMA_Close_{self.fast}", f"EMA_Close_{self.slow}" - ) and df["RSI"].iloc[-1] <= self.rsi_buy_threshold: - algorithm.create_limit_order( - target_symbol=target_symbol, - order_side=OrderSide.BUY, - price=price, - percentage_of_portfolio=25, - precision=4, - ) - - if algorithm.has_position(target_symbol) \ - and self.is_below_trend( - df, f"EMA_Close_{self.fast}", f"EMA_Close_{self.slow}" - ) and df["RSI"].iloc[-1] > self.rsi_sell_threshold: - open_trades = algorithm.get_open_trades( - target_symbol=target_symbol - ) - for trade in open_trades: - algorithm.close_trade(trade) - - def is_below_trend(self, data, fast_key, slow_key): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return data[fast_key].iloc[-1] < data[slow_key].iloc[-1] - - def is_crossover(self, data, fast_key, slow_key): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return data[fast_key].iloc[-2] <= data[slow_key].iloc[-2] \ - and data[fast_key].iloc[-1] > data[slow_key].iloc[-1] - - def add_ema(self, data, key, period): - data[f"EMA_{key}_{period}"] = tp.ema(data[key].to_numpy(), period) - return data - - def add_rsi(self, data, rsi_period): - # Calculate RSI - rsi_values = tp.rsi(data['Close'].to_numpy(), period=rsi_period) - - # Pad NaN values for initial rows with a default value, e.g., 0 - rsi_values = np.concatenate((np.full(rsi_period, 0), rsi_values)) - - # Assign RSI values to the DataFrame - data["RSI"] = rsi_values - return data - - -def create_algorithm( - name, - description, - short_period, - long_period, - rsi_period, - rsi_buy_threshold, - rsi_sell_threshold -) -> Algorithm: - algorithm = Algorithm( - name=name, - description=description - ) - algorithm.add_strategy( - Strategy( - short_period, - long_period, - rsi_period, - rsi_buy_threshold=rsi_buy_threshold, - rsi_sell_threshold=rsi_sell_threshold - ) - ) - return algorithm diff --git a/examples/backtests_example/algorithms/primary.py b/examples/backtests_example/algorithms/primary.py deleted file mode 100644 index cbf09c15..00000000 --- a/examples/backtests_example/algorithms/primary.py +++ /dev/null @@ -1,122 +0,0 @@ -import tulipy as tp - -from investing_algorithm_framework import Algorithm, TradingStrategy, \ - TimeUnit, OrderSide, CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource - -btc_eur_ohlcv_2h_data = CCXTOHLCVMarketDataSource( - identifier="BTC/EUR_ohlcv_2h", - symbol="BTC/EUR", - market="BITVAVO", - timeframe="2h", - window_size=200, -) - -btc_eur_ticker_data = CCXTTickerMarketDataSource( - identifier="BTC/EUR_ticker", - symbol="BTC/EUR", - market="BITVAVO", - backtest_timeframe="2h" -) - - -def is_below_trend(fast_series, slow_series): - return fast_series[-1] < slow_series[-1] - - -def is_above_trend(fast_series, slow_series): - return fast_series[-1] > slow_series[-1] - - -class Strategy(TradingStrategy): - time_unit = TimeUnit.HOUR - interval = 2 - market_data_sources = [ - btc_eur_ohlcv_2h_data, - btc_eur_ticker_data - ] - symbols = ["BTC/EUR"] - - def __init__( - self, - short_period, - long_period - ): - self.fast = short_period - self.slow = long_period - super().__init__() - - def apply_strategy(self, algorithm: Algorithm, market_data): - - for symbol in self.symbols: - target_symbol = symbol.split('/')[0] - - if algorithm.has_open_orders(target_symbol): - continue - - df = market_data[f"{symbol}_ohlcv_2h"].to_pandas() - df = self.add_ema(df, "Close", self.fast) - df = self.add_ema(df, "Close", self.slow) - ticker_data = market_data[f"{symbol}_ticker"] - price = ticker_data['bid'] - - if not algorithm.has_position(target_symbol) \ - and self.is_crossover( - df, f"EMA_Close_{self.fast}", f"EMA_Close_{self.slow}" - ): - algorithm.create_limit_order( - target_symbol=target_symbol, - order_side=OrderSide.BUY, - price=price, - percentage_of_portfolio=25, - precision=4, - ) - - if algorithm.has_position(target_symbol) \ - and self.is_below_trend( - df, f"EMA_Close_{self.fast}", f"EMA_Close_{self.slow}" - ): - open_trades = algorithm.get_open_trades( - target_symbol=target_symbol - ) - for trade in open_trades: - algorithm.close_trade(trade) - - def is_below_trend(self, data, fast_key, slow_key): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return data[fast_key].iloc[-1] < data[slow_key].iloc[-1] - - def is_crossover(self, data, fast_key, slow_key): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return data[fast_key].iloc[-2] <= data[slow_key].iloc[-2] \ - and data[fast_key].iloc[-1] > data[slow_key].iloc[-1] - - def add_ema(self, data, key, period): - data[f"EMA_{key}_{period}"] = tp.ema(data[key].to_numpy(), period) - return data - - -def create_algorithm( - name, - description, - short_period, - long_period -) -> Algorithm: - algorithm = Algorithm( - name=name, - description=description - ) - algorithm.add_strategy( - Strategy( - short_period, - long_period, - ) - ) - return algorithm diff --git a/examples/backtests_example/backtests.ipynb b/examples/backtests_example/backtests.ipynb index ba0adbc6..c3c6d9e4 100644 --- a/examples/backtests_example/backtests.ipynb +++ b/examples/backtests_example/backtests.ipynb @@ -2,54 +2,258 @@ "cells": [ { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ "# Experiment functionality tutorial with the Investing Algorithm Framework\n", "\n", "Welcome to the tutorial about the Experiment functionality within the Investing Algorithm Framework. In this guide, we'll demonstrate how to leverage this feature to conduct A/B Testing effectively. With Experiment, you can effortlessly compare the performance of multiple models, such as challenger versus champion, and experiment with diverse backtest date ranges and parameter configurations.\n", "\n", "The Experiment functionality proves invaluable for fine-tuning strategy parameters and comparing different strategies, whether it's testing a new challenger model against the existing production model (champion), or exploring variations in configuration. This process of comparing the challenger model to the production model, or champion, falls under the umbrella of model validation or model evaluation. It entails meticulously assessing the challenger model's performance in relation to the established production model." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", - "source": [ - "## Step 1: Importing the primary and challenger algorithms" - ], "metadata": { "collapsed": false - } + }, + "source": [ + "## Step 1: Define strategies for experimentation" + ] }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": 1, + "metadata": {}, "outputs": [], "source": [ - "# Import the factory methods for creation of the champion and challenger strategies\n", - "from algorithms import create_primary_algorithm, create_challenger_algorithm" + "from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \\\n", + " CCXTTickerMarketDataSource\n", + "\n", + "\n", + "btc_eur_ohlcv_2h_data = CCXTOHLCVMarketDataSource(\n", + " identifier=\"BTC/EUR_ohlcv_2h\",\n", + " symbol=\"BTC/EUR\",\n", + " market=\"BITVAVO\",\n", + " time_frame=\"2h\",\n", + " window_size=200,\n", + ")\n", + "\n", + "btc_eur_ticker_data = CCXTTickerMarketDataSource(\n", + " identifier=\"BTC/EUR_ticker\",\n", + " symbol=\"BTC/EUR\",\n", + " market=\"BITVAVO\",\n", + " backtest_time_frame=\"2h\"\n", + ")" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ - "## Step 2: Setting up the backtest" - ], + "from investing_algorithm_framework import Algorithm, TradingStrategy, \\\n", + " TimeUnit, OrderSide, get_ema, is_below, is_crossover, \\\n", + " convert_polars_to_pandas\n", + "\n", + "\n", + "class AlternativeStrategy(TradingStrategy):\n", + " time_unit = TimeUnit.HOUR\n", + " interval = 2\n", + " market_data_sources = [\n", + " btc_eur_ohlcv_2h_data,\n", + " btc_eur_ticker_data\n", + " ]\n", + " symbols = [\"BTC/EUR\"]\n", + "\n", + " def __init__(\n", + " self,\n", + " short_period,\n", + " long_period\n", + " ):\n", + " self.fast = short_period\n", + " self.slow = long_period\n", + " super().__init__()\n", + "\n", + " def apply_strategy(self, algorithm: Algorithm, market_data):\n", + "\n", + " for symbol in self.symbols:\n", + " target_symbol = symbol.split('/')[0]\n", + "\n", + " if algorithm.has_open_orders(target_symbol):\n", + " continue\n", + "\n", + " polars_df = market_data[f\"{symbol}_ohlcv_2h\"]\n", + " df = convert_polars_to_pandas(polars_df)\n", + " df = get_ema(data=df, source_column_name=\"Close\", period=self.fast)\n", + " df = get_ema(data=df, source_column_name=\"Close\", period=self.slow)\n", + " ticker_data = market_data[f\"{symbol}_ticker\"]\n", + " price = ticker_data['bid']\n", + "\n", + " if not algorithm.has_position(target_symbol) \\\n", + " and is_crossover(\n", + " df, f\"EMA_{self.fast}\", f\"EMA_{self.slow}\"\n", + " ):\n", + " algorithm.create_limit_order(\n", + " target_symbol=target_symbol,\n", + " order_side=OrderSide.BUY,\n", + " price=price,\n", + " percentage_of_portfolio=25,\n", + " precision=4,\n", + " )\n", + "\n", + " if algorithm.has_position(target_symbol) \\\n", + " and is_below(\n", + " df, f\"EMA_{self.fast}\", f\"EMA_{self.slow}\"\n", + " ):\n", + " open_trades = algorithm.get_open_trades(\n", + " target_symbol=target_symbol\n", + " )\n", + " for trade in open_trades:\n", + " algorithm.close_trade(trade)\n", + "\n", + "\n", + "def create_algorithm(\n", + " name,\n", + " description,\n", + " short_period,\n", + " long_period\n", + ") -> Algorithm:\n", + " algorithm = Algorithm(\n", + " name=name,\n", + " description=description\n", + " )\n", + " algorithm.add_strategy(\n", + " AlternativeStrategy(\n", + " short_period,\n", + " long_period,\n", + " )\n", + " )\n", + " return algorithm\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from investing_algorithm_framework import Algorithm, TradingStrategy, \\\n", + " TimeUnit, OrderSide, get_ema, get_rsi, is_crossover, is_below, \\\n", + " convert_polars_to_pandas\n", + "\n", + "\n", + "class Strategy(TradingStrategy):\n", + " time_unit = TimeUnit.HOUR\n", + " interval = 2\n", + " market_data_sources = [\n", + " btc_eur_ohlcv_2h_data,\n", + " btc_eur_ticker_data\n", + " ]\n", + " symbols = [\"BTC/EUR\"]\n", + "\n", + " def __init__(\n", + " self,\n", + " short_period,\n", + " long_period,\n", + " rsi_period,\n", + " rsi_buy_threshold,\n", + " rsi_sell_threshold\n", + " ):\n", + " self.fast = short_period\n", + " self.slow = long_period\n", + " self.rsi_period = rsi_period\n", + " self.rsi_buy_threshold = rsi_buy_threshold\n", + " self.rsi_sell_threshold = rsi_sell_threshold\n", + " super().__init__()\n", + "\n", + " def apply_strategy(self, algorithm: Algorithm, market_data):\n", + "\n", + " for symbol in self.symbols:\n", + " target_symbol = symbol.split('/')[0]\n", + "\n", + " if algorithm.has_open_orders(target_symbol):\n", + " continue\n", + "\n", + " polars_df = market_data[f\"{symbol}_ohlcv_2h\"]\n", + " df = convert_polars_to_pandas(polars_df)\n", + " df = get_ema(data=df, source_column_name=\"Close\", period=self.fast)\n", + " df = get_ema(data=df, source_column_name=\"Close\", period=self.slow)\n", + " df = get_rsi(data=df, period=self.rsi_period)\n", + " ticker_data = market_data[f\"{symbol}_ticker\"]\n", + " price = ticker_data['bid']\n", + "\n", + " if not algorithm.has_position(target_symbol) \\\n", + " and is_crossover(\n", + " df, f\"EMA_{self.fast}\", f\"EMA_{self.slow}\"\n", + " ) and df[f\"RSI_{self.rsi_period}\"].iloc[-1] <= self.rsi_buy_threshold:\n", + " algorithm.create_limit_order(\n", + " target_symbol=target_symbol,\n", + " order_side=OrderSide.BUY,\n", + " price=price,\n", + " percentage_of_portfolio=25,\n", + " precision=4,\n", + " )\n", + "\n", + " if algorithm.has_position(target_symbol) \\\n", + " and is_below(\n", + " df, f\"EMA_{self.fast}\", f\"EMA_{self.slow}\"\n", + " ) and df[f\"RSI_{self.rsi_period}\"].iloc[-1] > self.rsi_sell_threshold:\n", + " open_trades = algorithm.get_open_trades(\n", + " target_symbol=target_symbol\n", + " )\n", + " for trade in open_trades:\n", + " algorithm.close_trade(trade)\n", + "\n", + "def create_alternative_algorithm(\n", + " name,\n", + " description,\n", + " short_period,\n", + " long_period,\n", + " rsi_period,\n", + " rsi_buy_threshold,\n", + " rsi_sell_threshold\n", + ") -> Algorithm:\n", + " algorithm = Algorithm(\n", + " name=name,\n", + " description=description\n", + " )\n", + " algorithm.add_strategy(\n", + " Strategy(\n", + " short_period,\n", + " long_period,\n", + " rsi_period,\n", + " rsi_buy_threshold=rsi_buy_threshold,\n", + " rsi_sell_threshold=rsi_sell_threshold\n", + " )\n", + " )\n", + " return algorithm\n" + ] + }, + { + "cell_type": "markdown", "metadata": { "collapsed": false - } + }, + "source": [ + "## Step 2: Setting up the backtest" + ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, "data": [ { "line": { @@ -58,4354 +262,4354 @@ }, "mode": "lines", "name": "Close down turn", + "type": "scatter", "x": [ - "2021-12-21 02:00:00", - "2021-12-21 04:00:00", - "2021-12-21 06:00:00", - "2021-12-21 08:00:00", - "2021-12-21 10:00:00", - "2021-12-21 12:00:00", - "2021-12-21 14:00:00", - "2021-12-21 16:00:00", - "2021-12-21 18:00:00", - "2021-12-21 20:00:00", - "2021-12-21 22:00:00", - "2021-12-22 00:00:00", - "2021-12-22 02:00:00", - "2021-12-22 04:00:00", - "2021-12-22 06:00:00", - "2021-12-22 08:00:00", - "2021-12-22 10:00:00", - "2021-12-22 12:00:00", - "2021-12-22 14:00:00", - "2021-12-22 16:00:00", - "2021-12-22 18:00:00", - "2021-12-22 20:00:00", - "2021-12-22 22:00:00", - "2021-12-23 00:00:00", - "2021-12-23 02:00:00", - "2021-12-23 04:00:00", - "2021-12-23 06:00:00", - "2021-12-23 08:00:00", - "2021-12-23 10:00:00", - "2021-12-23 12:00:00", - "2021-12-23 14:00:00", - "2021-12-23 16:00:00", - "2021-12-23 18:00:00", - "2021-12-23 20:00:00", - "2021-12-23 22:00:00", - "2021-12-24 00:00:00", - "2021-12-24 02:00:00", - "2021-12-24 04:00:00", - "2021-12-24 06:00:00", - "2021-12-24 08:00:00", - "2021-12-24 10:00:00", - "2021-12-24 12:00:00", - "2021-12-24 14:00:00", - "2021-12-24 16:00:00", - "2021-12-24 18:00:00", - "2021-12-24 20:00:00", - "2021-12-24 22:00:00", - "2021-12-25 00:00:00", - "2021-12-25 02:00:00", - "2021-12-25 04:00:00", - "2021-12-25 06:00:00", - "2021-12-25 08:00:00", - "2021-12-25 10:00:00", - "2021-12-25 12:00:00", - "2021-12-25 14:00:00", - "2021-12-25 16:00:00", - "2021-12-25 18:00:00", - "2021-12-25 20:00:00", - "2021-12-25 22:00:00", - "2021-12-26 00:00:00", - "2021-12-26 02:00:00", - "2021-12-26 04:00:00", - "2021-12-26 06:00:00", - "2021-12-26 08:00:00", - "2021-12-26 10:00:00", - "2021-12-26 12:00:00", - "2021-12-26 14:00:00", - "2021-12-26 16:00:00", - "2021-12-26 18:00:00", - "2021-12-26 20:00:00", - "2021-12-26 22:00:00", - "2021-12-27 00:00:00", - "2021-12-27 02:00:00", - "2021-12-27 04:00:00", - "2021-12-27 06:00:00", - "2021-12-27 08:00:00", - "2021-12-27 10:00:00", - "2021-12-27 12:00:00", - "2021-12-27 14:00:00", - "2021-12-27 16:00:00", - "2021-12-27 18:00:00", - "2021-12-27 20:00:00", - "2021-12-27 22:00:00", - "2021-12-28 00:00:00", - "2021-12-28 02:00:00", - "2021-12-28 04:00:00", - "2021-12-28 06:00:00", - "2021-12-28 08:00:00", - "2021-12-28 10:00:00", - "2021-12-28 12:00:00", - "2021-12-28 14:00:00", - "2021-12-28 16:00:00", - "2021-12-28 18:00:00", - "2021-12-28 20:00:00", - "2021-12-28 22:00:00", - "2021-12-29 00:00:00", - "2021-12-29 02:00:00", - "2021-12-29 04:00:00", - "2021-12-29 06:00:00", - "2021-12-29 08:00:00", - "2021-12-29 10:00:00", - "2021-12-29 12:00:00", - "2021-12-29 14:00:00", - "2021-12-29 16:00:00", - "2021-12-29 18:00:00", - "2021-12-29 20:00:00", - "2021-12-29 22:00:00", - "2021-12-30 00:00:00", - "2021-12-30 02:00:00", - "2021-12-30 04:00:00", - "2021-12-30 06:00:00", - "2021-12-30 08:00:00", - "2021-12-30 10:00:00", - "2021-12-30 12:00:00", - "2021-12-30 14:00:00", - "2021-12-30 16:00:00", - "2021-12-30 18:00:00", - "2021-12-30 20:00:00", - "2021-12-30 22:00:00", - "2021-12-31 00:00:00", - "2021-12-31 02:00:00", - "2021-12-31 04:00:00", - "2021-12-31 06:00:00", - "2021-12-31 08:00:00", - "2021-12-31 10:00:00", - "2021-12-31 12:00:00", - "2021-12-31 14:00:00", - "2021-12-31 16:00:00", - "2021-12-31 18:00:00", - "2021-12-31 20:00:00", - "2021-12-31 22:00:00", - "2022-01-01 00:00:00", - "2022-01-01 02:00:00", - "2022-01-01 04:00:00", - "2022-01-01 06:00:00", - "2022-01-01 08:00:00", - "2022-01-01 10:00:00", - "2022-01-01 12:00:00", - "2022-01-01 14:00:00", - "2022-01-01 16:00:00", - "2022-01-01 18:00:00", - "2022-01-01 20:00:00", - "2022-01-01 22:00:00", - "2022-01-02 00:00:00", - "2022-01-02 02:00:00", - "2022-01-02 04:00:00", - "2022-01-02 06:00:00", - "2022-01-02 08:00:00", - "2022-01-02 10:00:00", - "2022-01-02 12:00:00", - "2022-01-02 14:00:00", - "2022-01-02 16:00:00", - "2022-01-02 18:00:00", - "2022-01-02 20:00:00", - "2022-01-02 22:00:00", - "2022-01-03 00:00:00", - "2022-01-03 02:00:00", - "2022-01-03 04:00:00", - "2022-01-03 06:00:00", - "2022-01-03 08:00:00", - "2022-01-03 10:00:00", - "2022-01-03 12:00:00", - "2022-01-03 14:00:00", - "2022-01-03 16:00:00", - "2022-01-03 18:00:00", - "2022-01-03 20:00:00", - "2022-01-03 22:00:00", - "2022-01-04 00:00:00", - "2022-01-04 02:00:00", - "2022-01-04 04:00:00", - "2022-01-04 06:00:00", - "2022-01-04 08:00:00", - "2022-01-04 10:00:00", - "2022-01-04 12:00:00", - "2022-01-04 14:00:00", - "2022-01-04 16:00:00", - "2022-01-04 18:00:00", - "2022-01-04 20:00:00", - "2022-01-04 22:00:00", - "2022-01-05 00:00:00", - "2022-01-05 02:00:00", - "2022-01-05 04:00:00", - "2022-01-05 06:00:00", - "2022-01-05 08:00:00", - "2022-01-05 10:00:00", - "2022-01-05 12:00:00", - "2022-01-05 14:00:00", - "2022-01-05 16:00:00", - "2022-01-05 18:00:00", - "2022-01-05 20:00:00", - "2022-01-05 22:00:00", - "2022-01-06 00:00:00", - "2022-01-06 02:00:00", - "2022-01-06 04:00:00", - "2022-01-06 06:00:00", - "2022-01-06 08:00:00", - "2022-01-06 10:00:00", - "2022-01-06 12:00:00", - "2022-01-06 14:00:00", - "2022-01-06 16:00:00", - "2022-01-06 18:00:00", - "2022-01-06 20:00:00", - "2022-01-06 22:00:00", - "2022-01-07 00:00:00", - "2022-01-07 02:00:00", - "2022-01-07 04:00:00", - "2022-01-07 06:00:00", - "2022-01-07 08:00:00", - "2022-01-07 10:00:00", - "2022-01-07 12:00:00", - "2022-01-07 14:00:00", - "2022-01-07 16:00:00", - "2022-01-07 18:00:00", - "2022-01-07 20:00:00", - "2022-01-07 22:00:00", - "2022-01-08 00:00:00", - "2022-01-08 02:00:00", - "2022-01-08 04:00:00", - "2022-01-08 06:00:00", - "2022-01-08 08:00:00", - "2022-01-08 10:00:00", - "2022-01-08 12:00:00", - "2022-01-08 14:00:00", - "2022-01-08 16:00:00", - "2022-01-08 18:00:00", - "2022-01-08 20:00:00", - "2022-01-08 22:00:00", - "2022-01-09 00:00:00", - "2022-01-09 02:00:00", - "2022-01-09 04:00:00", - "2022-01-09 06:00:00", - "2022-01-09 08:00:00", - "2022-01-09 10:00:00", - "2022-01-09 12:00:00", - "2022-01-09 14:00:00", - "2022-01-09 16:00:00", - "2022-01-09 18:00:00", - "2022-01-09 20:00:00", - "2022-01-09 22:00:00", - "2022-01-10 00:00:00", - "2022-01-10 02:00:00", - "2022-01-10 04:00:00", - "2022-01-10 06:00:00", - "2022-01-10 08:00:00", - "2022-01-10 10:00:00", - "2022-01-10 12:00:00", - "2022-01-10 14:00:00", - "2022-01-10 16:00:00", - "2022-01-10 18:00:00", - "2022-01-10 20:00:00", - "2022-01-10 22:00:00", - "2022-01-11 00:00:00", - "2022-01-11 02:00:00", - "2022-01-11 04:00:00", - "2022-01-11 06:00:00", - "2022-01-11 08:00:00", - "2022-01-11 10:00:00", - "2022-01-11 12:00:00", - "2022-01-11 14:00:00", - "2022-01-11 16:00:00", - "2022-01-11 18:00:00", - "2022-01-11 20:00:00", - "2022-01-11 22:00:00", - "2022-01-12 00:00:00", - "2022-01-12 02:00:00", - "2022-01-12 04:00:00", - "2022-01-12 06:00:00", - "2022-01-12 08:00:00", - "2022-01-12 10:00:00", - "2022-01-12 12:00:00", - "2022-01-12 14:00:00", - "2022-01-12 16:00:00", - "2022-01-12 18:00:00", - "2022-01-12 20:00:00", - "2022-01-12 22:00:00", - "2022-01-13 00:00:00", - "2022-01-13 02:00:00", - "2022-01-13 04:00:00", - "2022-01-13 06:00:00", - "2022-01-13 08:00:00", - "2022-01-13 10:00:00", - "2022-01-13 12:00:00", - "2022-01-13 14:00:00", - "2022-01-13 16:00:00", - "2022-01-13 18:00:00", - "2022-01-13 20:00:00", - "2022-01-13 22:00:00", - "2022-01-14 00:00:00", - "2022-01-14 02:00:00", - "2022-01-14 04:00:00", - "2022-01-14 06:00:00", - "2022-01-14 08:00:00", - "2022-01-14 10:00:00", - "2022-01-14 12:00:00", - "2022-01-14 14:00:00", - "2022-01-14 16:00:00", - "2022-01-14 18:00:00", - "2022-01-14 20:00:00", - "2022-01-14 22:00:00", - "2022-01-15 00:00:00", - "2022-01-15 02:00:00", - "2022-01-15 04:00:00", - "2022-01-15 06:00:00", - "2022-01-15 08:00:00", - "2022-01-15 10:00:00", - "2022-01-15 12:00:00", - "2022-01-15 14:00:00", - "2022-01-15 16:00:00", - "2022-01-15 18:00:00", - "2022-01-15 20:00:00", - "2022-01-15 22:00:00", - "2022-01-16 00:00:00", - "2022-01-16 02:00:00", - "2022-01-16 04:00:00", - "2022-01-16 06:00:00", - "2022-01-16 08:00:00", - "2022-01-16 10:00:00", - "2022-01-16 12:00:00", - "2022-01-16 14:00:00", - "2022-01-16 16:00:00", - "2022-01-16 18:00:00", - "2022-01-16 20:00:00", - "2022-01-16 22:00:00", - "2022-01-17 00:00:00", - "2022-01-17 02:00:00", - "2022-01-17 04:00:00", - "2022-01-17 06:00:00", - "2022-01-17 08:00:00", - "2022-01-17 10:00:00", - "2022-01-17 12:00:00", - "2022-01-17 14:00:00", - "2022-01-17 16:00:00", - "2022-01-17 18:00:00", - "2022-01-17 20:00:00", - "2022-01-17 22:00:00", - "2022-01-18 00:00:00", - "2022-01-18 02:00:00", - "2022-01-18 04:00:00", - "2022-01-18 06:00:00", - "2022-01-18 08:00:00", - "2022-01-18 10:00:00", - "2022-01-18 12:00:00", - "2022-01-18 14:00:00", - "2022-01-18 16:00:00", - "2022-01-18 18:00:00", - "2022-01-18 20:00:00", - "2022-01-18 22:00:00", - "2022-01-19 00:00:00", - "2022-01-19 02:00:00", - "2022-01-19 04:00:00", - "2022-01-19 06:00:00", - "2022-01-19 08:00:00", - "2022-01-19 10:00:00", - "2022-01-19 12:00:00", - "2022-01-19 14:00:00", - "2022-01-19 16:00:00", - "2022-01-19 18:00:00", - "2022-01-19 20:00:00", - "2022-01-19 22:00:00", - "2022-01-20 00:00:00", - "2022-01-20 02:00:00", - "2022-01-20 04:00:00", - "2022-01-20 06:00:00", - "2022-01-20 08:00:00", - "2022-01-20 10:00:00", - "2022-01-20 12:00:00", - "2022-01-20 14:00:00", - "2022-01-20 16:00:00", - "2022-01-20 18:00:00", - "2022-01-20 20:00:00", - "2022-01-20 22:00:00", - "2022-01-21 00:00:00", - "2022-01-21 02:00:00", - "2022-01-21 04:00:00", - "2022-01-21 06:00:00", - "2022-01-21 08:00:00", - "2022-01-21 10:00:00", - "2022-01-21 12:00:00", - "2022-01-21 14:00:00", - "2022-01-21 16:00:00", - "2022-01-21 18:00:00", - "2022-01-21 20:00:00", - "2022-01-21 22:00:00", - "2022-01-22 00:00:00", - "2022-01-22 02:00:00", - "2022-01-22 04:00:00", - "2022-01-22 06:00:00", - "2022-01-22 08:00:00", - "2022-01-22 10:00:00", - "2022-01-22 12:00:00", - "2022-01-22 14:00:00", - "2022-01-22 16:00:00", - "2022-01-22 18:00:00", - "2022-01-22 20:00:00", - "2022-01-22 22:00:00", - "2022-01-23 00:00:00", - "2022-01-23 02:00:00", - "2022-01-23 04:00:00", - "2022-01-23 06:00:00", - "2022-01-23 08:00:00", - "2022-01-23 10:00:00", - "2022-01-23 12:00:00", - "2022-01-23 14:00:00", - "2022-01-23 16:00:00", - "2022-01-23 18:00:00", - "2022-01-23 20:00:00", - "2022-01-23 22:00:00", - "2022-01-24 00:00:00", - "2022-01-24 02:00:00", - "2022-01-24 04:00:00", - "2022-01-24 06:00:00", - "2022-01-24 08:00:00", - "2022-01-24 10:00:00", - "2022-01-24 12:00:00", - "2022-01-24 14:00:00", - "2022-01-24 16:00:00", - "2022-01-24 18:00:00", - "2022-01-24 20:00:00", - "2022-01-24 22:00:00", - "2022-01-25 00:00:00", - "2022-01-25 02:00:00", - "2022-01-25 04:00:00", - "2022-01-25 06:00:00", - "2022-01-25 08:00:00", - "2022-01-25 10:00:00", - "2022-01-25 12:00:00", - "2022-01-25 14:00:00", - "2022-01-25 16:00:00", - "2022-01-25 18:00:00", - "2022-01-25 20:00:00", - "2022-01-25 22:00:00", - "2022-01-26 00:00:00", - "2022-01-26 02:00:00", - "2022-01-26 04:00:00", - "2022-01-26 06:00:00", - "2022-01-26 08:00:00", - "2022-01-26 10:00:00", - "2022-01-26 12:00:00", - "2022-01-26 14:00:00", - "2022-01-26 16:00:00", - "2022-01-26 18:00:00", - "2022-01-26 20:00:00", - "2022-01-26 22:00:00", - "2022-01-27 00:00:00", - "2022-01-27 02:00:00", - "2022-01-27 04:00:00", - "2022-01-27 06:00:00", - "2022-01-27 08:00:00", - "2022-01-27 10:00:00", - "2022-01-27 12:00:00", - "2022-01-27 14:00:00", - "2022-01-27 16:00:00", - "2022-01-27 18:00:00", - "2022-01-27 20:00:00", - "2022-01-27 22:00:00", - "2022-01-28 00:00:00", - "2022-01-28 02:00:00", - "2022-01-28 04:00:00", - "2022-01-28 06:00:00", - "2022-01-28 08:00:00", - "2022-01-28 10:00:00", - "2022-01-28 12:00:00", - "2022-01-28 14:00:00", - "2022-01-28 16:00:00", - "2022-01-28 18:00:00", - "2022-01-28 20:00:00", - "2022-01-28 22:00:00", - "2022-01-29 00:00:00", - "2022-01-29 02:00:00", - "2022-01-29 04:00:00", - "2022-01-29 06:00:00", - "2022-01-29 08:00:00", - "2022-01-29 10:00:00", - "2022-01-29 12:00:00", - "2022-01-29 14:00:00", - "2022-01-29 16:00:00", - "2022-01-29 18:00:00", - "2022-01-29 20:00:00", - "2022-01-29 22:00:00", - "2022-01-30 00:00:00", - "2022-01-30 02:00:00", - "2022-01-30 04:00:00", - "2022-01-30 06:00:00", - "2022-01-30 08:00:00", - "2022-01-30 10:00:00", - "2022-01-30 12:00:00", - "2022-01-30 14:00:00", - "2022-01-30 16:00:00", - "2022-01-30 18:00:00", - "2022-01-30 20:00:00", - "2022-01-30 22:00:00", - "2022-01-31 00:00:00", - "2022-01-31 02:00:00", - "2022-01-31 04:00:00", - "2022-01-31 06:00:00", - "2022-01-31 08:00:00", - "2022-01-31 10:00:00", - "2022-01-31 12:00:00", - "2022-01-31 14:00:00", - "2022-01-31 16:00:00", - "2022-01-31 18:00:00", - "2022-01-31 20:00:00", - "2022-01-31 22:00:00", - "2022-02-01 00:00:00", - "2022-02-01 02:00:00", - "2022-02-01 04:00:00", - "2022-02-01 06:00:00", - "2022-02-01 08:00:00", - "2022-02-01 10:00:00", - "2022-02-01 12:00:00", - "2022-02-01 14:00:00", - "2022-02-01 16:00:00", - "2022-02-01 18:00:00", - "2022-02-01 20:00:00", - "2022-02-01 22:00:00", - "2022-02-02 00:00:00", - "2022-02-02 02:00:00", - "2022-02-02 04:00:00", - "2022-02-02 06:00:00", - "2022-02-02 08:00:00", - "2022-02-02 10:00:00", - "2022-02-02 12:00:00", - "2022-02-02 14:00:00", - "2022-02-02 16:00:00", - "2022-02-02 18:00:00", - "2022-02-02 20:00:00", - "2022-02-02 22:00:00", - "2022-02-03 00:00:00", - "2022-02-03 02:00:00", - "2022-02-03 04:00:00", - "2022-02-03 06:00:00", - "2022-02-03 08:00:00", - "2022-02-03 10:00:00", - "2022-02-03 12:00:00", - "2022-02-03 14:00:00", - "2022-02-03 16:00:00", - "2022-02-03 18:00:00", - "2022-02-03 20:00:00", - "2022-02-03 22:00:00", - "2022-02-04 00:00:00", - "2022-02-04 02:00:00", - "2022-02-04 04:00:00", - "2022-02-04 06:00:00", - "2022-02-04 08:00:00", - "2022-02-04 10:00:00", - "2022-02-04 12:00:00", - "2022-02-04 14:00:00", - "2022-02-04 16:00:00", - "2022-02-04 18:00:00", - "2022-02-04 20:00:00", - "2022-02-04 22:00:00", - "2022-02-05 00:00:00", - "2022-02-05 02:00:00", - "2022-02-05 04:00:00", - "2022-02-05 06:00:00", - "2022-02-05 08:00:00", - "2022-02-05 10:00:00", - "2022-02-05 12:00:00", - "2022-02-05 14:00:00", - "2022-02-05 16:00:00", - "2022-02-05 18:00:00", - "2022-02-05 20:00:00", - "2022-02-05 22:00:00", - "2022-02-06 00:00:00", - "2022-02-06 02:00:00", - "2022-02-06 04:00:00", - "2022-02-06 06:00:00", - "2022-02-06 08:00:00", - "2022-02-06 10:00:00", - "2022-02-06 12:00:00", - "2022-02-06 14:00:00", - "2022-02-06 16:00:00", - "2022-02-06 18:00:00", - "2022-02-06 20:00:00", - "2022-02-06 22:00:00", - "2022-02-07 00:00:00", - "2022-02-07 02:00:00", - "2022-02-07 04:00:00", - "2022-02-07 06:00:00", - "2022-02-07 08:00:00", - "2022-02-07 10:00:00", - "2022-02-07 12:00:00", - "2022-02-07 14:00:00", - "2022-02-07 16:00:00", - "2022-02-07 18:00:00", - "2022-02-07 20:00:00", - "2022-02-07 22:00:00", - "2022-02-08 00:00:00", - "2022-02-08 02:00:00", - "2022-02-08 04:00:00", - "2022-02-08 06:00:00", - "2022-02-08 08:00:00", - "2022-02-08 10:00:00", - "2022-02-08 12:00:00", - "2022-02-08 14:00:00", - "2022-02-08 16:00:00", - "2022-02-08 18:00:00", - "2022-02-08 20:00:00", - "2022-02-08 22:00:00", - "2022-02-09 00:00:00", - "2022-02-09 02:00:00", - "2022-02-09 04:00:00", - "2022-02-09 06:00:00", - "2022-02-09 08:00:00", - "2022-02-09 10:00:00", - "2022-02-09 12:00:00", - "2022-02-09 14:00:00", - "2022-02-09 16:00:00", - "2022-02-09 18:00:00", - "2022-02-09 20:00:00", - "2022-02-09 22:00:00", - "2022-02-10 00:00:00", - "2022-02-10 02:00:00", - "2022-02-10 04:00:00", - "2022-02-10 06:00:00", - "2022-02-10 08:00:00", - "2022-02-10 10:00:00", - "2022-02-10 12:00:00", - "2022-02-10 14:00:00", - "2022-02-10 16:00:00", - "2022-02-10 18:00:00", - "2022-02-10 20:00:00", - "2022-02-10 22:00:00", - "2022-02-11 00:00:00", - "2022-02-11 02:00:00", - "2022-02-11 04:00:00", - "2022-02-11 06:00:00", - "2022-02-11 08:00:00", - "2022-02-11 10:00:00", - "2022-02-11 12:00:00", - "2022-02-11 14:00:00", - "2022-02-11 16:00:00", - "2022-02-11 18:00:00", - "2022-02-11 20:00:00", - "2022-02-11 22:00:00", - "2022-02-12 00:00:00", - "2022-02-12 02:00:00", - "2022-02-12 04:00:00", - "2022-02-12 06:00:00", - "2022-02-12 08:00:00", - "2022-02-12 10:00:00", - "2022-02-12 12:00:00", - "2022-02-12 14:00:00", - "2022-02-12 16:00:00", - "2022-02-12 18:00:00", - "2022-02-12 20:00:00", - "2022-02-12 22:00:00", - "2022-02-13 00:00:00", - "2022-02-13 02:00:00", - "2022-02-13 04:00:00", - "2022-02-13 06:00:00", - "2022-02-13 08:00:00", - "2022-02-13 10:00:00", - "2022-02-13 12:00:00", - "2022-02-13 14:00:00", - "2022-02-13 16:00:00", - "2022-02-13 18:00:00", - "2022-02-13 20:00:00", - "2022-02-13 22:00:00", - "2022-02-14 00:00:00", - "2022-02-14 02:00:00", - "2022-02-14 04:00:00", - "2022-02-14 06:00:00", - "2022-02-14 08:00:00", - "2022-02-14 10:00:00", - "2022-02-14 12:00:00", - "2022-02-14 14:00:00", - "2022-02-14 16:00:00", - "2022-02-14 18:00:00", - "2022-02-14 20:00:00", - "2022-02-14 22:00:00", - "2022-02-15 00:00:00", - "2022-02-15 02:00:00", - "2022-02-15 04:00:00", - "2022-02-15 06:00:00", - "2022-02-15 08:00:00", - "2022-02-15 10:00:00", - "2022-02-15 12:00:00", - "2022-02-15 14:00:00", - "2022-02-15 16:00:00", - "2022-02-15 18:00:00", - "2022-02-15 20:00:00", - "2022-02-15 22:00:00", - "2022-02-16 00:00:00", - "2022-02-16 02:00:00", - "2022-02-16 04:00:00", - "2022-02-16 06:00:00", - "2022-02-16 08:00:00", - "2022-02-16 10:00:00", - "2022-02-16 12:00:00", - "2022-02-16 14:00:00", - "2022-02-16 16:00:00", - "2022-02-16 18:00:00", - "2022-02-16 20:00:00", - "2022-02-16 22:00:00", - "2022-02-17 00:00:00", - "2022-02-17 02:00:00", - "2022-02-17 04:00:00", - "2022-02-17 06:00:00", - "2022-02-17 08:00:00", - "2022-02-17 10:00:00", - "2022-02-17 12:00:00", - "2022-02-17 14:00:00", - "2022-02-17 16:00:00", - "2022-02-17 18:00:00", - "2022-02-17 20:00:00", - "2022-02-17 22:00:00", - "2022-02-18 00:00:00", - "2022-02-18 02:00:00", - "2022-02-18 04:00:00", - "2022-02-18 06:00:00", - "2022-02-18 08:00:00", - "2022-02-18 10:00:00", - "2022-02-18 12:00:00", - "2022-02-18 14:00:00", - "2022-02-18 16:00:00", - "2022-02-18 18:00:00", - "2022-02-18 20:00:00", - "2022-02-18 22:00:00", - "2022-02-19 00:00:00", - "2022-02-19 02:00:00", - "2022-02-19 04:00:00", - "2022-02-19 06:00:00", - "2022-02-19 08:00:00", - "2022-02-19 10:00:00", - "2022-02-19 12:00:00", - "2022-02-19 14:00:00", - "2022-02-19 16:00:00", - "2022-02-19 18:00:00", - "2022-02-19 20:00:00", - "2022-02-19 22:00:00", - "2022-02-20 00:00:00", - "2022-02-20 02:00:00", - "2022-02-20 04:00:00", - "2022-02-20 06:00:00", - "2022-02-20 08:00:00", - "2022-02-20 10:00:00", - "2022-02-20 12:00:00", - "2022-02-20 14:00:00", - "2022-02-20 16:00:00", - "2022-02-20 18:00:00", - "2022-02-20 20:00:00", - "2022-02-20 22:00:00", - "2022-02-21 00:00:00", - "2022-02-21 02:00:00", - "2022-02-21 04:00:00", - "2022-02-21 06:00:00", - "2022-02-21 08:00:00", - "2022-02-21 10:00:00", - "2022-02-21 12:00:00", - "2022-02-21 14:00:00", - "2022-02-21 16:00:00", - "2022-02-21 18:00:00", - "2022-02-21 20:00:00", - "2022-02-21 22:00:00", - "2022-02-22 00:00:00", - "2022-02-22 02:00:00", - "2022-02-22 04:00:00", - "2022-02-22 06:00:00", - "2022-02-22 08:00:00", - "2022-02-22 10:00:00", - "2022-02-22 12:00:00", - "2022-02-22 14:00:00", - "2022-02-22 16:00:00", - "2022-02-22 18:00:00", - "2022-02-22 20:00:00", - "2022-02-22 22:00:00", - "2022-02-23 00:00:00", - "2022-02-23 02:00:00", - "2022-02-23 04:00:00", - "2022-02-23 06:00:00", - "2022-02-23 08:00:00", - "2022-02-23 10:00:00", - "2022-02-23 12:00:00", - "2022-02-23 14:00:00", - "2022-02-23 16:00:00", - "2022-02-23 18:00:00", - "2022-02-23 20:00:00", - "2022-02-23 22:00:00", - "2022-02-24 00:00:00", - "2022-02-24 02:00:00", - "2022-02-24 04:00:00", - "2022-02-24 06:00:00", - "2022-02-24 08:00:00", - "2022-02-24 10:00:00", - "2022-02-24 12:00:00", - "2022-02-24 14:00:00", - "2022-02-24 16:00:00", - "2022-02-24 18:00:00", - "2022-02-24 20:00:00", - "2022-02-24 22:00:00", - "2022-02-25 00:00:00", - "2022-02-25 02:00:00", - "2022-02-25 04:00:00", - "2022-02-25 06:00:00", - "2022-02-25 08:00:00", - "2022-02-25 10:00:00", - "2022-02-25 12:00:00", - "2022-02-25 14:00:00", - "2022-02-25 16:00:00", - "2022-02-25 18:00:00", - "2022-02-25 20:00:00", - "2022-02-25 22:00:00", - "2022-02-26 00:00:00", - "2022-02-26 02:00:00", - "2022-02-26 04:00:00", - "2022-02-26 06:00:00", - "2022-02-26 08:00:00", - "2022-02-26 10:00:00", - "2022-02-26 12:00:00", - "2022-02-26 14:00:00", - "2022-02-26 16:00:00", - "2022-02-26 18:00:00", - "2022-02-26 20:00:00", - "2022-02-26 22:00:00", - "2022-02-27 00:00:00", - "2022-02-27 02:00:00", - "2022-02-27 04:00:00", - "2022-02-27 06:00:00", - "2022-02-27 08:00:00", - "2022-02-27 10:00:00", - "2022-02-27 12:00:00", - "2022-02-27 14:00:00", - "2022-02-27 16:00:00", - "2022-02-27 18:00:00", - "2022-02-27 20:00:00", - "2022-02-27 22:00:00", - "2022-02-28 00:00:00", - "2022-02-28 02:00:00", - "2022-02-28 04:00:00", - "2022-02-28 06:00:00", - "2022-02-28 08:00:00", - "2022-02-28 10:00:00", - "2022-02-28 12:00:00", - "2022-02-28 14:00:00", - "2022-02-28 16:00:00", - "2022-02-28 18:00:00", - "2022-02-28 20:00:00", - "2022-02-28 22:00:00", - "2022-03-01 00:00:00", - "2022-03-01 02:00:00", - "2022-03-01 04:00:00", - "2022-03-01 06:00:00", - "2022-03-01 08:00:00", - "2022-03-01 10:00:00", - "2022-03-01 12:00:00", - "2022-03-01 14:00:00", - "2022-03-01 16:00:00", - "2022-03-01 18:00:00", - "2022-03-01 20:00:00", - "2022-03-01 22:00:00", - "2022-03-02 00:00:00", - "2022-03-02 02:00:00", - "2022-03-02 04:00:00", - "2022-03-02 06:00:00", - "2022-03-02 08:00:00", - "2022-03-02 10:00:00", - "2022-03-02 12:00:00", - "2022-03-02 14:00:00", - "2022-03-02 16:00:00", - "2022-03-02 18:00:00", - "2022-03-02 20:00:00", - "2022-03-02 22:00:00", - "2022-03-03 00:00:00", - "2022-03-03 02:00:00", - "2022-03-03 04:00:00", - "2022-03-03 06:00:00", - "2022-03-03 08:00:00", - "2022-03-03 10:00:00", - "2022-03-03 12:00:00", - "2022-03-03 14:00:00", - "2022-03-03 16:00:00", - "2022-03-03 18:00:00", - "2022-03-03 20:00:00", - "2022-03-03 22:00:00", - "2022-03-04 00:00:00", - "2022-03-04 02:00:00", - "2022-03-04 04:00:00", - "2022-03-04 06:00:00", - "2022-03-04 08:00:00", - "2022-03-04 10:00:00", - "2022-03-04 12:00:00", - "2022-03-04 14:00:00", - "2022-03-04 16:00:00", - "2022-03-04 18:00:00", - "2022-03-04 20:00:00", - "2022-03-04 22:00:00", - "2022-03-05 00:00:00", - "2022-03-05 02:00:00", - "2022-03-05 04:00:00", - "2022-03-05 06:00:00", - "2022-03-05 08:00:00", - "2022-03-05 10:00:00", - "2022-03-05 12:00:00", - "2022-03-05 14:00:00", - "2022-03-05 16:00:00", - "2022-03-05 18:00:00", - "2022-03-05 20:00:00", - "2022-03-05 22:00:00", - "2022-03-06 00:00:00", - "2022-03-06 02:00:00", - "2022-03-06 04:00:00", - "2022-03-06 06:00:00", - "2022-03-06 08:00:00", - "2022-03-06 10:00:00", - "2022-03-06 12:00:00", - "2022-03-06 14:00:00", - "2022-03-06 16:00:00", - "2022-03-06 18:00:00", - "2022-03-06 20:00:00", - "2022-03-06 22:00:00", - "2022-03-07 00:00:00", - "2022-03-07 02:00:00", - "2022-03-07 04:00:00", - "2022-03-07 06:00:00", - "2022-03-07 08:00:00", - "2022-03-07 10:00:00", - "2022-03-07 12:00:00", - "2022-03-07 14:00:00", - "2022-03-07 16:00:00", - "2022-03-07 18:00:00", - "2022-03-07 20:00:00", - "2022-03-07 22:00:00", - "2022-03-08 00:00:00", - "2022-03-08 02:00:00", - "2022-03-08 04:00:00", - "2022-03-08 06:00:00", - "2022-03-08 08:00:00", - "2022-03-08 10:00:00", - "2022-03-08 12:00:00", - "2022-03-08 14:00:00", - "2022-03-08 16:00:00", - "2022-03-08 18:00:00", - "2022-03-08 20:00:00", - "2022-03-08 22:00:00", - "2022-03-09 00:00:00", - "2022-03-09 02:00:00", - "2022-03-09 04:00:00", - "2022-03-09 06:00:00", - "2022-03-09 08:00:00", - "2022-03-09 10:00:00", - "2022-03-09 12:00:00", - "2022-03-09 14:00:00", - "2022-03-09 16:00:00", - "2022-03-09 18:00:00", - "2022-03-09 20:00:00", - "2022-03-09 22:00:00", - "2022-03-10 00:00:00", - "2022-03-10 02:00:00", - "2022-03-10 04:00:00", - "2022-03-10 06:00:00", - "2022-03-10 08:00:00", - "2022-03-10 10:00:00", - "2022-03-10 12:00:00", - "2022-03-10 14:00:00", - "2022-03-10 16:00:00", - "2022-03-10 18:00:00", - "2022-03-10 20:00:00", - "2022-03-10 22:00:00", - "2022-03-11 00:00:00", - "2022-03-11 02:00:00", - "2022-03-11 04:00:00", - "2022-03-11 06:00:00", - "2022-03-11 08:00:00", - "2022-03-11 10:00:00", - "2022-03-11 12:00:00", - "2022-03-11 14:00:00", - "2022-03-11 16:00:00", - "2022-03-11 18:00:00", - "2022-03-11 20:00:00", - "2022-03-11 22:00:00", - "2022-03-12 00:00:00", - "2022-03-12 02:00:00", - "2022-03-12 04:00:00", - "2022-03-12 06:00:00", - "2022-03-12 08:00:00", - "2022-03-12 10:00:00", - "2022-03-12 12:00:00", - "2022-03-12 14:00:00", - "2022-03-12 16:00:00", - "2022-03-12 18:00:00", - "2022-03-12 20:00:00", - "2022-03-12 22:00:00", - "2022-03-13 00:00:00", - "2022-03-13 02:00:00", - "2022-03-13 04:00:00", - "2022-03-13 06:00:00", - "2022-03-13 08:00:00", - "2022-03-13 10:00:00", - "2022-03-13 12:00:00", - "2022-03-13 14:00:00", - "2022-03-13 16:00:00", - "2022-03-13 18:00:00", - "2022-03-13 20:00:00", - "2022-03-13 22:00:00", - "2022-03-14 00:00:00", - "2022-03-14 02:00:00", - "2022-03-14 04:00:00", - "2022-03-14 06:00:00", - "2022-03-14 08:00:00", - "2022-03-14 10:00:00", - "2022-03-14 12:00:00", - "2022-03-14 14:00:00", - "2022-03-14 16:00:00", - "2022-03-14 18:00:00", - "2022-03-14 20:00:00", - "2022-03-14 22:00:00", - "2022-03-15 00:00:00", - "2022-03-15 02:00:00", - "2022-03-15 04:00:00", - "2022-03-15 06:00:00", - "2022-03-15 08:00:00", - "2022-03-15 10:00:00", - "2022-03-15 12:00:00", - "2022-03-15 14:00:00", - "2022-03-15 16:00:00", - "2022-03-15 18:00:00", - "2022-03-15 20:00:00", - "2022-03-15 22:00:00", - "2022-03-16 00:00:00", - "2022-03-16 02:00:00", - "2022-03-16 04:00:00", - "2022-03-16 06:00:00", - "2022-03-16 08:00:00", - "2022-03-16 10:00:00", - "2022-03-16 12:00:00", - "2022-03-16 14:00:00", - "2022-03-16 16:00:00", - "2022-03-16 18:00:00", - "2022-03-16 20:00:00", - "2022-03-16 22:00:00", - "2022-03-17 00:00:00", - "2022-03-17 02:00:00", - "2022-03-17 04:00:00", - "2022-03-17 06:00:00", - "2022-03-17 08:00:00", - "2022-03-17 10:00:00", - "2022-03-17 12:00:00", - "2022-03-17 14:00:00", - "2022-03-17 16:00:00", - "2022-03-17 18:00:00", - "2022-03-17 20:00:00", - "2022-03-17 22:00:00", - "2022-03-18 00:00:00", - "2022-03-18 02:00:00", - "2022-03-18 04:00:00", - "2022-03-18 06:00:00", - "2022-03-18 08:00:00", - "2022-03-18 10:00:00", - "2022-03-18 12:00:00", - "2022-03-18 14:00:00", - "2022-03-18 16:00:00", - "2022-03-18 18:00:00", - "2022-03-18 20:00:00", - "2022-03-18 22:00:00", - "2022-03-19 00:00:00", - "2022-03-19 02:00:00", - "2022-03-19 04:00:00", - "2022-03-19 06:00:00", - "2022-03-19 08:00:00", - "2022-03-19 10:00:00", - "2022-03-19 12:00:00", - "2022-03-19 14:00:00", - "2022-03-19 16:00:00", - "2022-03-19 18:00:00", - "2022-03-19 20:00:00", - "2022-03-19 22:00:00", - "2022-03-20 00:00:00", - "2022-03-20 02:00:00", - "2022-03-20 04:00:00", - "2022-03-20 06:00:00", - "2022-03-20 08:00:00", - "2022-03-20 10:00:00", - "2022-03-20 12:00:00", - "2022-03-20 14:00:00", - "2022-03-20 16:00:00", - "2022-03-20 18:00:00", - "2022-03-20 20:00:00", - "2022-03-20 22:00:00", - "2022-03-21 00:00:00", - "2022-03-21 02:00:00", - "2022-03-21 04:00:00", - "2022-03-21 06:00:00", - "2022-03-21 08:00:00", - "2022-03-21 10:00:00", - "2022-03-21 12:00:00", - "2022-03-21 14:00:00", - "2022-03-21 16:00:00", - "2022-03-21 18:00:00", - "2022-03-21 20:00:00", - "2022-03-21 22:00:00", - "2022-03-22 00:00:00", - "2022-03-22 02:00:00", - "2022-03-22 04:00:00", - "2022-03-22 06:00:00", - "2022-03-22 08:00:00", - "2022-03-22 10:00:00", - "2022-03-22 12:00:00", - "2022-03-22 14:00:00", - "2022-03-22 16:00:00", - "2022-03-22 18:00:00", - "2022-03-22 20:00:00", - "2022-03-22 22:00:00", - "2022-03-23 00:00:00", - "2022-03-23 02:00:00", - "2022-03-23 04:00:00", - "2022-03-23 06:00:00", - "2022-03-23 08:00:00", - "2022-03-23 10:00:00", - "2022-03-23 12:00:00", - "2022-03-23 14:00:00", - "2022-03-23 16:00:00", - "2022-03-23 18:00:00", - "2022-03-23 20:00:00", - "2022-03-23 22:00:00", - "2022-03-24 00:00:00", - "2022-03-24 02:00:00", - "2022-03-24 04:00:00", - "2022-03-24 06:00:00", - "2022-03-24 08:00:00", - "2022-03-24 10:00:00", - "2022-03-24 12:00:00", - "2022-03-24 14:00:00", - "2022-03-24 16:00:00", - "2022-03-24 18:00:00", - "2022-03-24 20:00:00", - "2022-03-24 22:00:00", - "2022-03-25 00:00:00", - "2022-03-25 02:00:00", - "2022-03-25 04:00:00", - "2022-03-25 06:00:00", - "2022-03-25 08:00:00", - "2022-03-25 10:00:00", - "2022-03-25 12:00:00", - "2022-03-25 14:00:00", - "2022-03-25 16:00:00", - "2022-03-25 18:00:00", - "2022-03-25 20:00:00", - "2022-03-25 22:00:00", - "2022-03-26 00:00:00", - "2022-03-26 02:00:00", - "2022-03-26 04:00:00", - "2022-03-26 06:00:00", - "2022-03-26 08:00:00", - "2022-03-26 10:00:00", - "2022-03-26 12:00:00", - "2022-03-26 14:00:00", - "2022-03-26 16:00:00", - "2022-03-26 18:00:00", - "2022-03-26 20:00:00", - "2022-03-26 22:00:00", - "2022-03-27 00:00:00", - "2022-03-27 02:00:00", - "2022-03-27 04:00:00", - "2022-03-27 06:00:00", - "2022-03-27 08:00:00", - "2022-03-27 10:00:00", - "2022-03-27 12:00:00", - "2022-03-27 14:00:00", - "2022-03-27 16:00:00", - "2022-03-27 18:00:00", - "2022-03-27 20:00:00", - "2022-03-27 22:00:00", - "2022-03-28 00:00:00", - "2022-03-28 02:00:00", - "2022-03-28 04:00:00", - "2022-03-28 06:00:00", - "2022-03-28 08:00:00", - "2022-03-28 10:00:00", - "2022-03-28 12:00:00", - "2022-03-28 14:00:00", - "2022-03-28 16:00:00", - "2022-03-28 18:00:00", - "2022-03-28 20:00:00", - "2022-03-28 22:00:00", - "2022-03-29 00:00:00", - "2022-03-29 02:00:00", - "2022-03-29 04:00:00", - "2022-03-29 06:00:00", - "2022-03-29 08:00:00", - "2022-03-29 10:00:00", - "2022-03-29 12:00:00", - "2022-03-29 14:00:00", - "2022-03-29 16:00:00", - "2022-03-29 18:00:00", - "2022-03-29 20:00:00", - "2022-03-29 22:00:00", - "2022-03-30 00:00:00", - "2022-03-30 02:00:00", - "2022-03-30 04:00:00", - "2022-03-30 06:00:00", - "2022-03-30 08:00:00", - "2022-03-30 10:00:00", - "2022-03-30 12:00:00", - "2022-03-30 14:00:00", - "2022-03-30 16:00:00", - "2022-03-30 18:00:00", - "2022-03-30 20:00:00", - "2022-03-30 22:00:00", - "2022-03-31 00:00:00", - "2022-03-31 02:00:00", - "2022-03-31 04:00:00", - "2022-03-31 06:00:00", - "2022-03-31 08:00:00", - "2022-03-31 10:00:00", - "2022-03-31 12:00:00", - "2022-03-31 14:00:00", - "2022-03-31 16:00:00", - "2022-03-31 18:00:00", - "2022-03-31 20:00:00", - "2022-03-31 22:00:00", - "2022-04-01 00:00:00", - "2022-04-01 02:00:00", - "2022-04-01 04:00:00", - "2022-04-01 06:00:00", - "2022-04-01 08:00:00", - "2022-04-01 10:00:00", - "2022-04-01 12:00:00", - "2022-04-01 14:00:00", - "2022-04-01 16:00:00", - "2022-04-01 18:00:00", - "2022-04-01 20:00:00", - "2022-04-01 22:00:00", - "2022-04-02 00:00:00", - "2022-04-02 02:00:00", - "2022-04-02 04:00:00", - "2022-04-02 06:00:00", - "2022-04-02 08:00:00", - "2022-04-02 10:00:00", - "2022-04-02 12:00:00", - "2022-04-02 14:00:00", - "2022-04-02 16:00:00", - "2022-04-02 18:00:00", - "2022-04-02 20:00:00", - "2022-04-02 22:00:00", - "2022-04-03 00:00:00", - "2022-04-03 02:00:00", - "2022-04-03 04:00:00", - "2022-04-03 06:00:00", - "2022-04-03 08:00:00", - "2022-04-03 10:00:00", - "2022-04-03 12:00:00", - "2022-04-03 14:00:00", - "2022-04-03 16:00:00", - "2022-04-03 18:00:00", - "2022-04-03 20:00:00", - "2022-04-03 22:00:00", - "2022-04-04 00:00:00", - "2022-04-04 02:00:00", - "2022-04-04 04:00:00", - "2022-04-04 06:00:00", - "2022-04-04 08:00:00", - "2022-04-04 10:00:00", - "2022-04-04 12:00:00", - "2022-04-04 14:00:00", - "2022-04-04 16:00:00", - "2022-04-04 18:00:00", - "2022-04-04 20:00:00", - "2022-04-04 22:00:00", - "2022-04-05 00:00:00", - "2022-04-05 02:00:00", - "2022-04-05 04:00:00", - "2022-04-05 06:00:00", - "2022-04-05 08:00:00", - "2022-04-05 10:00:00", - "2022-04-05 12:00:00", - "2022-04-05 14:00:00", - "2022-04-05 16:00:00", - "2022-04-05 18:00:00", - "2022-04-05 20:00:00", - "2022-04-05 22:00:00", - "2022-04-06 00:00:00", - "2022-04-06 02:00:00", - "2022-04-06 04:00:00", - "2022-04-06 06:00:00", - "2022-04-06 08:00:00", - "2022-04-06 10:00:00", - "2022-04-06 12:00:00", - "2022-04-06 14:00:00", - "2022-04-06 16:00:00", - "2022-04-06 18:00:00", - "2022-04-06 20:00:00", - "2022-04-06 22:00:00", - "2022-04-07 00:00:00", - "2022-04-07 02:00:00", - "2022-04-07 04:00:00", - "2022-04-07 06:00:00", - "2022-04-07 08:00:00", - "2022-04-07 10:00:00", - "2022-04-07 12:00:00", - "2022-04-07 14:00:00", - "2022-04-07 16:00:00", - "2022-04-07 18:00:00", - "2022-04-07 20:00:00", - "2022-04-07 22:00:00", - "2022-04-08 00:00:00", - "2022-04-08 02:00:00", - "2022-04-08 04:00:00", - "2022-04-08 06:00:00", - "2022-04-08 08:00:00", - "2022-04-08 10:00:00", - "2022-04-08 12:00:00", - "2022-04-08 14:00:00", - "2022-04-08 16:00:00", - "2022-04-08 18:00:00", - "2022-04-08 20:00:00", - "2022-04-08 22:00:00", - "2022-04-09 00:00:00", - "2022-04-09 02:00:00", - "2022-04-09 04:00:00", - "2022-04-09 06:00:00", - "2022-04-09 08:00:00", - "2022-04-09 10:00:00", - "2022-04-09 12:00:00", - "2022-04-09 14:00:00", - "2022-04-09 16:00:00", - "2022-04-09 18:00:00", - "2022-04-09 20:00:00", - "2022-04-09 22:00:00", - "2022-04-10 00:00:00", - "2022-04-10 02:00:00", - "2022-04-10 04:00:00", - "2022-04-10 06:00:00", - "2022-04-10 08:00:00", - "2022-04-10 10:00:00", - "2022-04-10 12:00:00", - "2022-04-10 14:00:00", - "2022-04-10 16:00:00", - "2022-04-10 18:00:00", - "2022-04-10 20:00:00", - "2022-04-10 22:00:00", - "2022-04-11 00:00:00", - "2022-04-11 02:00:00", - "2022-04-11 04:00:00", - "2022-04-11 06:00:00", - "2022-04-11 08:00:00", - "2022-04-11 10:00:00", - "2022-04-11 12:00:00", - "2022-04-11 14:00:00", - "2022-04-11 16:00:00", - "2022-04-11 18:00:00", - "2022-04-11 20:00:00", - "2022-04-11 22:00:00", - "2022-04-12 00:00:00", - "2022-04-12 02:00:00", - "2022-04-12 04:00:00", - "2022-04-12 06:00:00", - "2022-04-12 08:00:00", - "2022-04-12 10:00:00", - "2022-04-12 12:00:00", - "2022-04-12 14:00:00", - "2022-04-12 16:00:00", - "2022-04-12 18:00:00", - "2022-04-12 20:00:00", - "2022-04-12 22:00:00", - "2022-04-13 00:00:00", - "2022-04-13 02:00:00", - "2022-04-13 04:00:00", - "2022-04-13 06:00:00", - "2022-04-13 08:00:00", - "2022-04-13 10:00:00", - "2022-04-13 12:00:00", - "2022-04-13 14:00:00", - "2022-04-13 16:00:00", - "2022-04-13 18:00:00", - "2022-04-13 20:00:00", - "2022-04-13 22:00:00", - "2022-04-14 00:00:00", - "2022-04-14 02:00:00", - "2022-04-14 04:00:00", - "2022-04-14 06:00:00", - "2022-04-14 08:00:00", - "2022-04-14 10:00:00", - "2022-04-14 12:00:00", - "2022-04-14 14:00:00", - "2022-04-14 16:00:00", - "2022-04-14 18:00:00", - "2022-04-14 20:00:00", - "2022-04-14 22:00:00", - "2022-04-15 00:00:00", - "2022-04-15 02:00:00", - "2022-04-15 04:00:00", - "2022-04-15 06:00:00", - "2022-04-15 08:00:00", - "2022-04-15 10:00:00", - "2022-04-15 12:00:00", - "2022-04-15 14:00:00", - "2022-04-15 16:00:00", - "2022-04-15 18:00:00", - "2022-04-15 20:00:00", - "2022-04-15 22:00:00", - "2022-04-16 00:00:00", - "2022-04-16 02:00:00", - "2022-04-16 04:00:00", - "2022-04-16 06:00:00", - "2022-04-16 08:00:00", - "2022-04-16 10:00:00", - "2022-04-16 12:00:00", - "2022-04-16 14:00:00", - "2022-04-16 16:00:00", - "2022-04-16 18:00:00", - "2022-04-16 20:00:00", - "2022-04-16 22:00:00", - "2022-04-17 00:00:00", - "2022-04-17 02:00:00", - "2022-04-17 04:00:00", - "2022-04-17 06:00:00", - "2022-04-17 08:00:00", - "2022-04-17 10:00:00", - "2022-04-17 12:00:00", - "2022-04-17 14:00:00", - "2022-04-17 16:00:00", - "2022-04-17 18:00:00", - "2022-04-17 20:00:00", - "2022-04-17 22:00:00", - "2022-04-18 00:00:00", - "2022-04-18 02:00:00", - "2022-04-18 04:00:00", - "2022-04-18 06:00:00", - "2022-04-18 08:00:00", - "2022-04-18 10:00:00", - "2022-04-18 12:00:00", - "2022-04-18 14:00:00", - "2022-04-18 16:00:00", - "2022-04-18 18:00:00", - "2022-04-18 20:00:00", - "2022-04-18 22:00:00", - "2022-04-19 00:00:00", - "2022-04-19 02:00:00", - "2022-04-19 04:00:00", - "2022-04-19 06:00:00", - "2022-04-19 08:00:00", - "2022-04-19 10:00:00", - "2022-04-19 12:00:00", - "2022-04-19 14:00:00", - "2022-04-19 16:00:00", - "2022-04-19 18:00:00", - "2022-04-19 20:00:00", - "2022-04-19 22:00:00", - "2022-04-20 00:00:00", - "2022-04-20 04:00:00", - "2022-04-20 06:00:00", - "2022-04-20 08:00:00", - "2022-04-20 10:00:00", - "2022-04-20 12:00:00", - "2022-04-20 14:00:00", - "2022-04-20 16:00:00", - "2022-04-20 18:00:00", - "2022-04-20 20:00:00", - "2022-04-20 22:00:00", - "2022-04-21 00:00:00", - "2022-04-21 02:00:00", - "2022-04-21 04:00:00", - "2022-04-21 06:00:00", - "2022-04-21 08:00:00", - "2022-04-21 10:00:00", - "2022-04-21 12:00:00", - "2022-04-21 14:00:00", - "2022-04-21 16:00:00", - "2022-04-21 18:00:00", - "2022-04-21 20:00:00", - "2022-04-21 22:00:00", - "2022-04-22 00:00:00", - "2022-04-22 02:00:00", - "2022-04-22 04:00:00", - "2022-04-22 06:00:00", - "2022-04-22 08:00:00", - "2022-04-22 10:00:00", - "2022-04-22 12:00:00", - "2022-04-22 14:00:00", - "2022-04-22 16:00:00", - "2022-04-22 18:00:00", - "2022-04-22 20:00:00", - "2022-04-22 22:00:00", - "2022-04-23 00:00:00", - "2022-04-23 02:00:00", - "2022-04-23 04:00:00", - "2022-04-23 06:00:00", - "2022-04-23 08:00:00", - "2022-04-23 10:00:00", - "2022-04-23 12:00:00", - "2022-04-23 14:00:00", - "2022-04-23 16:00:00", - "2022-04-23 18:00:00", - "2022-04-23 20:00:00", - "2022-04-23 22:00:00", - "2022-04-24 00:00:00", - "2022-04-24 02:00:00", - "2022-04-24 04:00:00", - "2022-04-24 06:00:00", - "2022-04-24 08:00:00", - "2022-04-24 10:00:00", - "2022-04-24 12:00:00", - "2022-04-24 14:00:00", - "2022-04-24 16:00:00", - "2022-04-24 18:00:00", - "2022-04-24 20:00:00", - "2022-04-24 22:00:00", - "2022-04-25 00:00:00", - "2022-04-25 02:00:00", - "2022-04-25 04:00:00", - "2022-04-25 06:00:00", - "2022-04-25 08:00:00", - "2022-04-25 10:00:00", - "2022-04-25 12:00:00", - "2022-04-25 14:00:00", - "2022-04-25 16:00:00", - "2022-04-25 18:00:00", - "2022-04-25 20:00:00", - "2022-04-25 22:00:00", - "2022-04-26 00:00:00", - "2022-04-26 02:00:00", - "2022-04-26 04:00:00", - "2022-04-26 06:00:00", - "2022-04-26 08:00:00", - "2022-04-26 10:00:00", - "2022-04-26 12:00:00", - "2022-04-26 14:00:00", - "2022-04-26 16:00:00", - "2022-04-26 18:00:00", - "2022-04-26 20:00:00", - "2022-04-26 22:00:00", - "2022-04-27 00:00:00", - "2022-04-27 02:00:00", - "2022-04-27 04:00:00", - "2022-04-27 06:00:00", - "2022-04-27 08:00:00", - "2022-04-27 10:00:00", - "2022-04-27 12:00:00", - "2022-04-27 14:00:00", - "2022-04-27 16:00:00", - "2022-04-27 18:00:00", - "2022-04-27 20:00:00", - "2022-04-27 22:00:00", - "2022-04-28 00:00:00", - "2022-04-28 02:00:00", - "2022-04-28 04:00:00", - "2022-04-28 06:00:00", - "2022-04-28 08:00:00", - "2022-04-28 10:00:00", - "2022-04-28 12:00:00", - "2022-04-28 14:00:00", - "2022-04-28 16:00:00", - "2022-04-28 18:00:00", - "2022-04-28 20:00:00", - "2022-04-28 22:00:00", - "2022-04-29 00:00:00", - "2022-04-29 02:00:00", - "2022-04-29 04:00:00", - "2022-04-29 06:00:00", - "2022-04-29 08:00:00", - "2022-04-29 10:00:00", - "2022-04-29 12:00:00", - "2022-04-29 14:00:00", - "2022-04-29 16:00:00", - "2022-04-29 18:00:00", - "2022-04-29 20:00:00", - "2022-04-29 22:00:00", - "2022-04-30 00:00:00", - "2022-04-30 02:00:00", - "2022-04-30 04:00:00", - "2022-04-30 06:00:00", - "2022-04-30 08:00:00", - "2022-04-30 10:00:00", - "2022-04-30 12:00:00", - "2022-04-30 14:00:00", - "2022-04-30 16:00:00", - "2022-04-30 18:00:00", - "2022-04-30 20:00:00", - "2022-04-30 22:00:00", - "2022-05-01 00:00:00", - "2022-05-01 02:00:00", - "2022-05-01 04:00:00", - "2022-05-01 06:00:00", - "2022-05-01 08:00:00", - "2022-05-01 10:00:00", - "2022-05-01 12:00:00", - "2022-05-01 14:00:00", - "2022-05-01 16:00:00", - "2022-05-01 18:00:00", - "2022-05-01 20:00:00", - "2022-05-01 22:00:00", - "2022-05-02 00:00:00", - "2022-05-02 02:00:00", - "2022-05-02 04:00:00", - "2022-05-02 06:00:00", - "2022-05-02 08:00:00", - "2022-05-02 10:00:00", - "2022-05-02 12:00:00", - "2022-05-02 14:00:00", - "2022-05-02 16:00:00", - "2022-05-02 18:00:00", - "2022-05-02 20:00:00", - "2022-05-02 22:00:00", - "2022-05-03 00:00:00", - "2022-05-03 02:00:00", - "2022-05-03 04:00:00", - "2022-05-03 06:00:00", - "2022-05-03 08:00:00", - "2022-05-03 10:00:00", - "2022-05-03 12:00:00", - "2022-05-03 14:00:00", - "2022-05-03 16:00:00", - "2022-05-03 18:00:00", - "2022-05-03 20:00:00", - "2022-05-03 22:00:00", - "2022-05-04 00:00:00", - "2022-05-04 02:00:00", - "2022-05-04 04:00:00", - "2022-05-04 06:00:00", - "2022-05-04 08:00:00", - "2022-05-04 10:00:00", - "2022-05-04 12:00:00", - "2022-05-04 14:00:00", - "2022-05-04 16:00:00", - "2022-05-04 18:00:00", - "2022-05-04 20:00:00", - "2022-05-04 22:00:00", - "2022-05-05 00:00:00", - "2022-05-05 02:00:00", - "2022-05-05 04:00:00", - "2022-05-05 06:00:00", - "2022-05-05 08:00:00", - "2022-05-05 10:00:00", - "2022-05-05 12:00:00", - "2022-05-05 14:00:00", - "2022-05-05 16:00:00", - "2022-05-05 18:00:00", - "2022-05-05 20:00:00", - "2022-05-05 22:00:00", - "2022-05-06 00:00:00", - "2022-05-06 02:00:00", - "2022-05-06 04:00:00", - "2022-05-06 06:00:00", - "2022-05-06 08:00:00", - "2022-05-06 10:00:00", - "2022-05-06 12:00:00", - "2022-05-06 14:00:00", - "2022-05-06 16:00:00", - "2022-05-06 18:00:00", - "2022-05-06 20:00:00", - "2022-05-06 22:00:00", - "2022-05-07 00:00:00", - "2022-05-07 02:00:00", - "2022-05-07 04:00:00", - "2022-05-07 06:00:00", - "2022-05-07 08:00:00", - "2022-05-07 10:00:00", - "2022-05-07 12:00:00", - "2022-05-07 14:00:00", - "2022-05-07 16:00:00", - "2022-05-07 18:00:00", - "2022-05-07 20:00:00", - "2022-05-07 22:00:00", - "2022-05-08 00:00:00", - "2022-05-08 02:00:00", - "2022-05-08 04:00:00", - "2022-05-08 06:00:00", - "2022-05-08 08:00:00", - "2022-05-08 10:00:00", - "2022-05-08 12:00:00", - "2022-05-08 14:00:00", - "2022-05-08 16:00:00", - "2022-05-08 18:00:00", - "2022-05-08 20:00:00", - "2022-05-08 22:00:00", - "2022-05-09 00:00:00", - "2022-05-09 02:00:00", - "2022-05-09 04:00:00", - "2022-05-09 06:00:00", - "2022-05-09 08:00:00", - "2022-05-09 10:00:00", - "2022-05-09 12:00:00", - "2022-05-09 14:00:00", - "2022-05-09 16:00:00", - "2022-05-09 18:00:00", - "2022-05-09 20:00:00", - "2022-05-09 22:00:00", - "2022-05-10 00:00:00", - "2022-05-10 02:00:00", - "2022-05-10 04:00:00", - "2022-05-10 06:00:00", - "2022-05-10 08:00:00", - "2022-05-10 10:00:00", - "2022-05-10 12:00:00", - "2022-05-10 14:00:00", - "2022-05-10 16:00:00", - "2022-05-10 18:00:00", - "2022-05-10 20:00:00", - "2022-05-10 22:00:00", - "2022-05-11 00:00:00", - "2022-05-11 02:00:00", - "2022-05-11 04:00:00", - "2022-05-11 06:00:00", - "2022-05-11 08:00:00", - "2022-05-11 10:00:00", - "2022-05-11 12:00:00", - "2022-05-11 14:00:00", - "2022-05-11 16:00:00", - "2022-05-11 18:00:00", - "2022-05-11 20:00:00", - "2022-05-11 22:00:00", - "2022-05-12 00:00:00", - "2022-05-12 02:00:00", - "2022-05-12 04:00:00", - "2022-05-12 06:00:00", - "2022-05-12 08:00:00", - "2022-05-12 10:00:00", - "2022-05-12 12:00:00", - "2022-05-12 14:00:00", - "2022-05-12 16:00:00", - "2022-05-12 18:00:00", - "2022-05-12 20:00:00", - "2022-05-12 22:00:00", - "2022-05-13 00:00:00", - "2022-05-13 02:00:00", - "2022-05-13 04:00:00", - "2022-05-13 06:00:00", - "2022-05-13 08:00:00", - "2022-05-13 10:00:00", - "2022-05-13 12:00:00", - "2022-05-13 14:00:00", - "2022-05-13 16:00:00", - "2022-05-13 18:00:00", - "2022-05-13 20:00:00", - "2022-05-13 22:00:00", - "2022-05-14 00:00:00", - "2022-05-14 02:00:00", - "2022-05-14 04:00:00", - "2022-05-14 06:00:00", - "2022-05-14 08:00:00", - "2022-05-14 10:00:00", - "2022-05-14 12:00:00", - "2022-05-14 14:00:00", - "2022-05-14 16:00:00", - "2022-05-14 18:00:00", - "2022-05-14 20:00:00", - "2022-05-14 22:00:00", - "2022-05-15 00:00:00", - "2022-05-15 02:00:00", - "2022-05-15 04:00:00", - "2022-05-15 06:00:00", - "2022-05-15 08:00:00", - "2022-05-15 10:00:00", - "2022-05-15 12:00:00", - "2022-05-15 14:00:00", - "2022-05-15 16:00:00", - "2022-05-15 18:00:00", - "2022-05-15 20:00:00", - "2022-05-15 22:00:00", - "2022-05-16 00:00:00", - "2022-05-16 02:00:00", - "2022-05-16 04:00:00", - "2022-05-16 06:00:00", - "2022-05-16 08:00:00", - "2022-05-16 10:00:00", - "2022-05-16 12:00:00", - "2022-05-16 14:00:00", - "2022-05-16 16:00:00", - "2022-05-16 18:00:00", - "2022-05-16 20:00:00", - "2022-05-16 22:00:00", - "2022-05-17 00:00:00", - "2022-05-17 02:00:00", - "2022-05-17 04:00:00", - "2022-05-17 06:00:00", - "2022-05-17 08:00:00", - "2022-05-17 10:00:00", - "2022-05-17 12:00:00", - "2022-05-17 14:00:00", - "2022-05-17 16:00:00", - "2022-05-17 18:00:00", - "2022-05-17 20:00:00", - "2022-05-17 22:00:00", - "2022-05-18 00:00:00", - "2022-05-18 02:00:00", - "2022-05-18 04:00:00", - "2022-05-18 06:00:00", - "2022-05-18 08:00:00", - "2022-05-18 10:00:00", - "2022-05-18 12:00:00", - "2022-05-18 14:00:00", - "2022-05-18 16:00:00", - "2022-05-18 18:00:00", - "2022-05-18 20:00:00", - "2022-05-18 22:00:00", - "2022-05-19 00:00:00", - "2022-05-19 02:00:00", - "2022-05-19 04:00:00", - "2022-05-19 06:00:00", - "2022-05-19 08:00:00", - "2022-05-19 10:00:00", - "2022-05-19 12:00:00", - "2022-05-19 14:00:00", - "2022-05-19 16:00:00", - "2022-05-19 18:00:00", - "2022-05-19 20:00:00", - "2022-05-19 22:00:00", - "2022-05-20 00:00:00", - "2022-05-20 02:00:00", - "2022-05-20 04:00:00", - "2022-05-20 06:00:00", - "2022-05-20 08:00:00", - "2022-05-20 10:00:00", - "2022-05-20 12:00:00", - "2022-05-20 14:00:00", - "2022-05-20 16:00:00", - "2022-05-20 18:00:00", - "2022-05-20 20:00:00", - "2022-05-20 22:00:00", - "2022-05-21 00:00:00", - "2022-05-21 02:00:00", - "2022-05-21 04:00:00", - "2022-05-21 06:00:00", - "2022-05-21 08:00:00", - "2022-05-21 10:00:00", - "2022-05-21 12:00:00", - "2022-05-21 14:00:00", - "2022-05-21 16:00:00", - "2022-05-21 18:00:00", - "2022-05-21 20:00:00", - "2022-05-21 22:00:00", - "2022-05-22 00:00:00", - "2022-05-22 02:00:00", - "2022-05-22 04:00:00", - "2022-05-22 06:00:00", - "2022-05-22 08:00:00", - "2022-05-22 10:00:00", - "2022-05-22 12:00:00", - "2022-05-22 14:00:00", - "2022-05-22 16:00:00", - "2022-05-22 18:00:00", - "2022-05-22 20:00:00", - "2022-05-22 22:00:00", - "2022-05-23 00:00:00", - "2022-05-23 02:00:00", - "2022-05-23 04:00:00", - "2022-05-23 06:00:00", - "2022-05-23 08:00:00", - "2022-05-23 10:00:00", - "2022-05-23 12:00:00", - "2022-05-23 14:00:00", - "2022-05-23 16:00:00", - "2022-05-23 18:00:00", - "2022-05-23 20:00:00", - "2022-05-23 22:00:00", - "2022-05-24 00:00:00", - "2022-05-24 02:00:00", - "2022-05-24 04:00:00", - "2022-05-24 06:00:00", - "2022-05-24 08:00:00", - "2022-05-24 10:00:00", - "2022-05-24 12:00:00", - "2022-05-24 14:00:00", - "2022-05-24 16:00:00", - "2022-05-24 18:00:00", - "2022-05-24 20:00:00", - "2022-05-24 22:00:00", - "2022-05-25 00:00:00", - "2022-05-25 02:00:00", - "2022-05-25 04:00:00", - "2022-05-25 06:00:00", - "2022-05-25 08:00:00", - "2022-05-25 10:00:00", - "2022-05-25 12:00:00", - "2022-05-25 14:00:00", - "2022-05-25 16:00:00", - "2022-05-25 18:00:00", - "2022-05-25 20:00:00", - "2022-05-25 22:00:00", - "2022-05-26 00:00:00", - "2022-05-26 02:00:00", - "2022-05-26 04:00:00", - "2022-05-26 06:00:00", - "2022-05-26 08:00:00", - "2022-05-26 10:00:00", - "2022-05-26 12:00:00", - "2022-05-26 14:00:00", - "2022-05-26 16:00:00", - "2022-05-26 18:00:00", - "2022-05-26 20:00:00", - "2022-05-26 22:00:00", - "2022-05-27 00:00:00", - "2022-05-27 02:00:00", - "2022-05-27 04:00:00", - "2022-05-27 06:00:00", - "2022-05-27 08:00:00", - "2022-05-27 10:00:00", - "2022-05-27 12:00:00", - "2022-05-27 14:00:00", - "2022-05-27 16:00:00", - "2022-05-27 18:00:00", - "2022-05-27 20:00:00", - "2022-05-27 22:00:00", - "2022-05-28 00:00:00", - "2022-05-28 02:00:00", - "2022-05-28 04:00:00", - "2022-05-28 06:00:00", - "2022-05-28 08:00:00", - "2022-05-28 10:00:00", - "2022-05-28 12:00:00", - "2022-05-28 14:00:00", - "2022-05-28 16:00:00", - "2022-05-28 18:00:00", - "2022-05-28 20:00:00", - "2022-05-28 22:00:00", - "2022-05-29 00:00:00", - "2022-05-29 02:00:00", - "2022-05-29 04:00:00", - "2022-05-29 06:00:00", - "2022-05-29 08:00:00", - "2022-05-29 10:00:00", - "2022-05-29 12:00:00", - "2022-05-29 14:00:00", - "2022-05-29 16:00:00", - "2022-05-29 18:00:00", - "2022-05-29 20:00:00", - "2022-05-29 22:00:00", - "2022-05-30 00:00:00", - "2022-05-30 02:00:00", - "2022-05-30 04:00:00", - "2022-05-30 06:00:00", - "2022-05-30 08:00:00", - "2022-05-30 10:00:00", - "2022-05-30 12:00:00", - "2022-05-30 14:00:00", - "2022-05-30 16:00:00", - "2022-05-30 18:00:00", - "2022-05-30 20:00:00", - "2022-05-30 22:00:00", - "2022-05-31 00:00:00", - "2022-05-31 02:00:00", - "2022-05-31 04:00:00", - "2022-05-31 06:00:00", - "2022-05-31 08:00:00", - "2022-05-31 10:00:00", - "2022-05-31 12:00:00", - "2022-05-31 14:00:00", - "2022-05-31 16:00:00", - "2022-05-31 18:00:00", - "2022-05-31 20:00:00", - "2022-05-31 22:00:00", - "2022-06-01 00:00:00", - "2022-06-01 02:00:00", - "2022-06-01 04:00:00", - "2022-06-01 06:00:00", - "2022-06-01 08:00:00", - "2022-06-01 10:00:00", - "2022-06-01 12:00:00", - "2022-06-01 14:00:00", - "2022-06-01 16:00:00", - "2022-06-01 18:00:00", - "2022-06-01 20:00:00", - "2022-06-01 22:00:00", - "2022-06-02 00:00:00", - "2022-06-02 02:00:00", - "2022-06-02 04:00:00", - "2022-06-02 06:00:00", - "2022-06-02 08:00:00", - "2022-06-02 10:00:00", - "2022-06-02 12:00:00", - "2022-06-02 14:00:00", - "2022-06-02 16:00:00", - "2022-06-02 18:00:00", - "2022-06-02 20:00:00", - "2022-06-02 22:00:00", - "2022-06-03 00:00:00", - "2022-06-03 02:00:00", - "2022-06-03 04:00:00", - "2022-06-03 06:00:00", - "2022-06-03 08:00:00", - "2022-06-03 10:00:00", - "2022-06-03 12:00:00", - "2022-06-03 14:00:00", - "2022-06-03 16:00:00", - "2022-06-03 18:00:00", - "2022-06-03 20:00:00", - "2022-06-03 22:00:00", - "2022-06-04 00:00:00", - "2022-06-04 02:00:00", - "2022-06-04 04:00:00", - "2022-06-04 06:00:00", - "2022-06-04 08:00:00", - "2022-06-04 10:00:00", - "2022-06-04 12:00:00", - "2022-06-04 14:00:00", - "2022-06-04 16:00:00", - "2022-06-04 18:00:00", - "2022-06-04 20:00:00", - "2022-06-04 22:00:00", - "2022-06-05 00:00:00", - "2022-06-05 02:00:00", - "2022-06-05 04:00:00", - "2022-06-05 06:00:00", - "2022-06-05 08:00:00", - "2022-06-05 10:00:00", - "2022-06-05 12:00:00", - "2022-06-05 14:00:00", - "2022-06-05 16:00:00", - "2022-06-05 18:00:00", - "2022-06-05 20:00:00", - "2022-06-05 22:00:00", - "2022-06-06 00:00:00", - "2022-06-06 02:00:00", - "2022-06-06 04:00:00", - "2022-06-06 06:00:00", - "2022-06-06 08:00:00", - "2022-06-06 10:00:00", - "2022-06-06 12:00:00", - "2022-06-06 14:00:00", - "2022-06-06 16:00:00", - "2022-06-06 18:00:00", - "2022-06-06 20:00:00", - "2022-06-06 22:00:00", - "2022-06-07 00:00:00", - "2022-06-07 02:00:00", - "2022-06-07 04:00:00", - "2022-06-07 06:00:00", - "2022-06-07 08:00:00", - "2022-06-07 10:00:00", - "2022-06-07 12:00:00", - "2022-06-07 14:00:00", - "2022-06-07 16:00:00", - "2022-06-07 18:00:00", - "2022-06-07 20:00:00", - "2022-06-07 22:00:00", - "2022-06-08 00:00:00", - "2022-06-08 02:00:00", - "2022-06-08 04:00:00", - "2022-06-08 06:00:00", - "2022-06-08 08:00:00", - "2022-06-08 10:00:00", - "2022-06-08 12:00:00", - "2022-06-08 14:00:00", - "2022-06-08 16:00:00", - "2022-06-08 18:00:00", - "2022-06-08 20:00:00", - "2022-06-08 22:00:00", - "2022-06-09 00:00:00", - "2022-06-09 02:00:00", - "2022-06-09 04:00:00", - "2022-06-09 06:00:00", - "2022-06-09 08:00:00", - "2022-06-09 10:00:00", - "2022-06-09 12:00:00", - "2022-06-09 14:00:00", - "2022-06-09 16:00:00", - "2022-06-09 18:00:00", - "2022-06-09 20:00:00", - "2022-06-09 22:00:00", - "2022-06-10 00:00:00", - "2022-06-10 02:00:00", - "2022-06-10 04:00:00", - "2022-06-10 06:00:00", - "2022-06-10 08:00:00", - "2022-06-10 10:00:00", - "2022-06-10 12:00:00", - "2022-06-10 14:00:00", - "2022-06-10 16:00:00", - "2022-06-10 18:00:00", - "2022-06-10 20:00:00", - "2022-06-10 22:00:00", - "2022-06-11 00:00:00", - "2022-06-11 02:00:00", - "2022-06-11 04:00:00", - "2022-06-11 06:00:00", - "2022-06-11 08:00:00", - "2022-06-11 10:00:00", - "2022-06-11 12:00:00", - "2022-06-11 14:00:00", - "2022-06-11 16:00:00", - "2022-06-11 18:00:00", - "2022-06-11 20:00:00", - "2022-06-11 22:00:00", - "2022-06-12 00:00:00", - "2022-06-12 02:00:00", - "2022-06-12 04:00:00", - "2022-06-12 06:00:00", - "2022-06-12 08:00:00", - "2022-06-12 10:00:00", - "2022-06-12 12:00:00", - "2022-06-12 14:00:00", - "2022-06-12 16:00:00", - "2022-06-12 18:00:00", - "2022-06-12 20:00:00", - "2022-06-12 22:00:00", - "2022-06-13 00:00:00", - "2022-06-13 02:00:00", - "2022-06-13 04:00:00", - "2022-06-13 06:00:00", - "2022-06-13 08:00:00", - "2022-06-13 10:00:00", - "2022-06-13 12:00:00", - "2022-06-13 14:00:00", - "2022-06-13 16:00:00", - "2022-06-13 18:00:00", - "2022-06-13 20:00:00", - "2022-06-13 22:00:00", - "2022-06-14 00:00:00", - "2022-06-14 02:00:00", - "2022-06-14 04:00:00", - "2022-06-14 06:00:00", - "2022-06-14 08:00:00", - "2022-06-14 10:00:00", - "2022-06-14 12:00:00", - "2022-06-14 14:00:00", - "2022-06-14 16:00:00", - "2022-06-14 18:00:00", - "2022-06-14 20:00:00", - "2022-06-14 22:00:00", - "2022-06-15 00:00:00", - "2022-06-15 02:00:00", - "2022-06-15 04:00:00", - "2022-06-15 06:00:00", - "2022-06-15 08:00:00", - "2022-06-15 10:00:00", - "2022-06-15 12:00:00", - "2022-06-15 14:00:00", - "2022-06-15 16:00:00", - "2022-06-15 18:00:00", - "2022-06-15 20:00:00", - "2022-06-15 22:00:00", - "2022-06-16 00:00:00", - "2022-06-16 02:00:00", - "2022-06-16 04:00:00", - "2022-06-16 06:00:00", - "2022-06-16 08:00:00", - "2022-06-16 10:00:00", - "2022-06-16 12:00:00", - "2022-06-16 14:00:00", - "2022-06-16 16:00:00", - "2022-06-16 18:00:00", - "2022-06-16 20:00:00", - "2022-06-16 22:00:00", - "2022-06-17 00:00:00", - "2022-06-17 02:00:00", - "2022-06-17 04:00:00", - "2022-06-17 06:00:00", - "2022-06-17 08:00:00", - "2022-06-17 10:00:00", - "2022-06-17 12:00:00", - "2022-06-17 14:00:00", - "2022-06-17 16:00:00", - "2022-06-17 18:00:00", - "2022-06-17 20:00:00", - "2022-06-17 22:00:00", - "2022-06-18 00:00:00", - "2022-06-18 02:00:00", - "2022-06-18 04:00:00", - "2022-06-18 06:00:00", - "2022-06-18 08:00:00", - "2022-06-18 10:00:00", - "2022-06-18 12:00:00", - "2022-06-18 14:00:00", - "2022-06-18 16:00:00", - "2022-06-18 18:00:00", - "2022-06-18 20:00:00", - "2022-06-18 22:00:00", - "2022-06-19 00:00:00", - "2022-06-19 02:00:00", - "2022-06-19 04:00:00", - "2022-06-19 06:00:00", - "2022-06-19 08:00:00", - "2022-06-19 10:00:00", - "2022-06-19 12:00:00", - "2022-06-19 14:00:00", - "2022-06-19 16:00:00", - "2022-06-19 18:00:00", - "2022-06-19 20:00:00", - "2022-06-19 22:00:00", - "2022-06-20 00:00:00" + "2021-12-21T02:00:00", + "2021-12-21T04:00:00", + "2021-12-21T06:00:00", + "2021-12-21T08:00:00", + "2021-12-21T10:00:00", + "2021-12-21T12:00:00", + "2021-12-21T14:00:00", + "2021-12-21T16:00:00", + "2021-12-21T18:00:00", + "2021-12-21T20:00:00", + "2021-12-21T22:00:00", + "2021-12-22T00:00:00", + "2021-12-22T02:00:00", + "2021-12-22T04:00:00", + "2021-12-22T06:00:00", + "2021-12-22T08:00:00", + "2021-12-22T10:00:00", + "2021-12-22T12:00:00", + "2021-12-22T14:00:00", + "2021-12-22T16:00:00", + "2021-12-22T18:00:00", + "2021-12-22T20:00:00", + "2021-12-22T22:00:00", + "2021-12-23T00:00:00", + "2021-12-23T02:00:00", + "2021-12-23T04:00:00", + "2021-12-23T06:00:00", + "2021-12-23T08:00:00", + "2021-12-23T10:00:00", + "2021-12-23T12:00:00", + "2021-12-23T14:00:00", + "2021-12-23T16:00:00", + "2021-12-23T18:00:00", + "2021-12-23T20:00:00", + "2021-12-23T22:00:00", + "2021-12-24T00:00:00", + "2021-12-24T02:00:00", + "2021-12-24T04:00:00", + "2021-12-24T06:00:00", + "2021-12-24T08:00:00", + "2021-12-24T10:00:00", + "2021-12-24T12:00:00", + "2021-12-24T14:00:00", + "2021-12-24T16:00:00", + "2021-12-24T18:00:00", + "2021-12-24T20:00:00", + "2021-12-24T22:00:00", + "2021-12-25T00:00:00", + "2021-12-25T02:00:00", + "2021-12-25T04:00:00", + "2021-12-25T06:00:00", + "2021-12-25T08:00:00", + "2021-12-25T10:00:00", + "2021-12-25T12:00:00", + "2021-12-25T14:00:00", + "2021-12-25T16:00:00", + "2021-12-25T18:00:00", + "2021-12-25T20:00:00", + "2021-12-25T22:00:00", + "2021-12-26T00:00:00", + "2021-12-26T02:00:00", + "2021-12-26T04:00:00", + "2021-12-26T06:00:00", + "2021-12-26T08:00:00", + "2021-12-26T10:00:00", + "2021-12-26T12:00:00", + "2021-12-26T14:00:00", + "2021-12-26T16:00:00", + "2021-12-26T18:00:00", + "2021-12-26T20:00:00", + "2021-12-26T22:00:00", + "2021-12-27T00:00:00", + "2021-12-27T02:00:00", + "2021-12-27T04:00:00", + "2021-12-27T06:00:00", + "2021-12-27T08:00:00", + "2021-12-27T10:00:00", + "2021-12-27T12:00:00", + "2021-12-27T14:00:00", + "2021-12-27T16:00:00", + "2021-12-27T18:00:00", + "2021-12-27T20:00:00", + "2021-12-27T22:00:00", + "2021-12-28T00:00:00", + "2021-12-28T02:00:00", + "2021-12-28T04:00:00", + "2021-12-28T06:00:00", + "2021-12-28T08:00:00", + "2021-12-28T10:00:00", + "2021-12-28T12:00:00", + "2021-12-28T14:00:00", + "2021-12-28T16:00:00", + "2021-12-28T18:00:00", + "2021-12-28T20:00:00", + "2021-12-28T22:00:00", + "2021-12-29T00:00:00", + "2021-12-29T02:00:00", + "2021-12-29T04:00:00", + "2021-12-29T06:00:00", + "2021-12-29T08:00:00", + "2021-12-29T10:00:00", + "2021-12-29T12:00:00", + "2021-12-29T14:00:00", + "2021-12-29T16:00:00", + "2021-12-29T18:00:00", + "2021-12-29T20:00:00", + "2021-12-29T22:00:00", + "2021-12-30T00:00:00", + "2021-12-30T02:00:00", + "2021-12-30T04:00:00", + "2021-12-30T06:00:00", + "2021-12-30T08:00:00", + "2021-12-30T10:00:00", + "2021-12-30T12:00:00", + "2021-12-30T14:00:00", + "2021-12-30T16:00:00", + "2021-12-30T18:00:00", + "2021-12-30T20:00:00", + "2021-12-30T22:00:00", + "2021-12-31T00:00:00", + "2021-12-31T02:00:00", + "2021-12-31T04:00:00", + "2021-12-31T06:00:00", + "2021-12-31T08:00:00", + "2021-12-31T10:00:00", + "2021-12-31T12:00:00", + "2021-12-31T14:00:00", + "2021-12-31T16:00:00", + "2021-12-31T18:00:00", + "2021-12-31T20:00:00", + "2021-12-31T22:00:00", + "2022-01-01T00:00:00", + "2022-01-01T02:00:00", + "2022-01-01T04:00:00", + "2022-01-01T06:00:00", + "2022-01-01T08:00:00", + "2022-01-01T10:00:00", + "2022-01-01T12:00:00", + "2022-01-01T14:00:00", + "2022-01-01T16:00:00", + "2022-01-01T18:00:00", + "2022-01-01T20:00:00", + "2022-01-01T22:00:00", + "2022-01-02T00:00:00", + "2022-01-02T02:00:00", + "2022-01-02T04:00:00", + "2022-01-02T06:00:00", + "2022-01-02T08:00:00", + "2022-01-02T10:00:00", + "2022-01-02T12:00:00", + "2022-01-02T14:00:00", + "2022-01-02T16:00:00", + "2022-01-02T18:00:00", + "2022-01-02T20:00:00", + "2022-01-02T22:00:00", + "2022-01-03T00:00:00", + "2022-01-03T02:00:00", + "2022-01-03T04:00:00", + "2022-01-03T06:00:00", + "2022-01-03T08:00:00", + "2022-01-03T10:00:00", + "2022-01-03T12:00:00", + "2022-01-03T14:00:00", + "2022-01-03T16:00:00", + "2022-01-03T18:00:00", + "2022-01-03T20:00:00", + "2022-01-03T22:00:00", + "2022-01-04T00:00:00", + "2022-01-04T02:00:00", + "2022-01-04T04:00:00", + "2022-01-04T06:00:00", + "2022-01-04T08:00:00", + "2022-01-04T10:00:00", + "2022-01-04T12:00:00", + "2022-01-04T14:00:00", + "2022-01-04T16:00:00", + "2022-01-04T18:00:00", + "2022-01-04T20:00:00", + "2022-01-04T22:00:00", + "2022-01-05T00:00:00", + "2022-01-05T02:00:00", + "2022-01-05T04:00:00", + "2022-01-05T06:00:00", + "2022-01-05T08:00:00", + "2022-01-05T10:00:00", + "2022-01-05T12:00:00", + "2022-01-05T14:00:00", + "2022-01-05T16:00:00", + "2022-01-05T18:00:00", + "2022-01-05T20:00:00", + "2022-01-05T22:00:00", + "2022-01-06T00:00:00", + "2022-01-06T02:00:00", + "2022-01-06T04:00:00", + "2022-01-06T06:00:00", + "2022-01-06T08:00:00", + "2022-01-06T10:00:00", + "2022-01-06T12:00:00", + "2022-01-06T14:00:00", + "2022-01-06T16:00:00", + "2022-01-06T18:00:00", + "2022-01-06T20:00:00", + "2022-01-06T22:00:00", + "2022-01-07T00:00:00", + "2022-01-07T02:00:00", + "2022-01-07T04:00:00", + "2022-01-07T06:00:00", + "2022-01-07T08:00:00", + "2022-01-07T10:00:00", + "2022-01-07T12:00:00", + "2022-01-07T14:00:00", + "2022-01-07T16:00:00", + "2022-01-07T18:00:00", + "2022-01-07T20:00:00", + "2022-01-07T22:00:00", + "2022-01-08T00:00:00", + "2022-01-08T02:00:00", + "2022-01-08T04:00:00", + "2022-01-08T06:00:00", + "2022-01-08T08:00:00", + "2022-01-08T10:00:00", + "2022-01-08T12:00:00", + "2022-01-08T14:00:00", + "2022-01-08T16:00:00", + "2022-01-08T18:00:00", + "2022-01-08T20:00:00", + "2022-01-08T22:00:00", + "2022-01-09T00:00:00", + "2022-01-09T02:00:00", + "2022-01-09T04:00:00", + "2022-01-09T06:00:00", + "2022-01-09T08:00:00", + "2022-01-09T10:00:00", + "2022-01-09T12:00:00", + "2022-01-09T14:00:00", + "2022-01-09T16:00:00", + "2022-01-09T18:00:00", + "2022-01-09T20:00:00", + "2022-01-09T22:00:00", + "2022-01-10T00:00:00", + "2022-01-10T02:00:00", + "2022-01-10T04:00:00", + "2022-01-10T06:00:00", + "2022-01-10T08:00:00", + "2022-01-10T10:00:00", + "2022-01-10T12:00:00", + "2022-01-10T14:00:00", + "2022-01-10T16:00:00", + "2022-01-10T18:00:00", + "2022-01-10T20:00:00", + "2022-01-10T22:00:00", + "2022-01-11T00:00:00", + "2022-01-11T02:00:00", + "2022-01-11T04:00:00", + "2022-01-11T06:00:00", + "2022-01-11T08:00:00", + "2022-01-11T10:00:00", + "2022-01-11T12:00:00", + "2022-01-11T14:00:00", + "2022-01-11T16:00:00", + "2022-01-11T18:00:00", + "2022-01-11T20:00:00", + "2022-01-11T22:00:00", + "2022-01-12T00:00:00", + "2022-01-12T02:00:00", + "2022-01-12T04:00:00", + "2022-01-12T06:00:00", + "2022-01-12T08:00:00", + "2022-01-12T10:00:00", + "2022-01-12T12:00:00", + "2022-01-12T14:00:00", + "2022-01-12T16:00:00", + "2022-01-12T18:00:00", + "2022-01-12T20:00:00", + "2022-01-12T22:00:00", + "2022-01-13T00:00:00", + "2022-01-13T02:00:00", + "2022-01-13T04:00:00", + "2022-01-13T06:00:00", + "2022-01-13T08:00:00", + "2022-01-13T10:00:00", + "2022-01-13T12:00:00", + "2022-01-13T14:00:00", + "2022-01-13T16:00:00", + "2022-01-13T18:00:00", + "2022-01-13T20:00:00", + "2022-01-13T22:00:00", + "2022-01-14T00:00:00", + "2022-01-14T02:00:00", + "2022-01-14T04:00:00", + "2022-01-14T06:00:00", + "2022-01-14T08:00:00", + "2022-01-14T10:00:00", + "2022-01-14T12:00:00", + "2022-01-14T14:00:00", + "2022-01-14T16:00:00", + "2022-01-14T18:00:00", + "2022-01-14T20:00:00", + "2022-01-14T22:00:00", + "2022-01-15T00:00:00", + "2022-01-15T02:00:00", + "2022-01-15T04:00:00", + "2022-01-15T06:00:00", + "2022-01-15T08:00:00", + "2022-01-15T10:00:00", + "2022-01-15T12:00:00", + "2022-01-15T14:00:00", + "2022-01-15T16:00:00", + "2022-01-15T18:00:00", + "2022-01-15T20:00:00", + "2022-01-15T22:00:00", + "2022-01-16T00:00:00", + "2022-01-16T02:00:00", + "2022-01-16T04:00:00", + "2022-01-16T06:00:00", + "2022-01-16T08:00:00", + "2022-01-16T10:00:00", + "2022-01-16T12:00:00", + "2022-01-16T14:00:00", + "2022-01-16T16:00:00", + "2022-01-16T18:00:00", + "2022-01-16T20:00:00", + "2022-01-16T22:00:00", + "2022-01-17T00:00:00", + "2022-01-17T02:00:00", + "2022-01-17T04:00:00", + "2022-01-17T06:00:00", + "2022-01-17T08:00:00", + "2022-01-17T10:00:00", + "2022-01-17T12:00:00", + "2022-01-17T14:00:00", + "2022-01-17T16:00:00", + "2022-01-17T18:00:00", + "2022-01-17T20:00:00", + "2022-01-17T22:00:00", + "2022-01-18T00:00:00", + "2022-01-18T02:00:00", + "2022-01-18T04:00:00", + "2022-01-18T06:00:00", + "2022-01-18T08:00:00", + "2022-01-18T10:00:00", + "2022-01-18T12:00:00", + "2022-01-18T14:00:00", + "2022-01-18T16:00:00", + "2022-01-18T18:00:00", + "2022-01-18T20:00:00", + "2022-01-18T22:00:00", + "2022-01-19T00:00:00", + "2022-01-19T02:00:00", + "2022-01-19T04:00:00", + "2022-01-19T06:00:00", + "2022-01-19T08:00:00", + "2022-01-19T10:00:00", + "2022-01-19T12:00:00", + "2022-01-19T14:00:00", + "2022-01-19T16:00:00", + "2022-01-19T18:00:00", + "2022-01-19T20:00:00", + "2022-01-19T22:00:00", + "2022-01-20T00:00:00", + "2022-01-20T02:00:00", + "2022-01-20T04:00:00", + "2022-01-20T06:00:00", + "2022-01-20T08:00:00", + "2022-01-20T10:00:00", + "2022-01-20T12:00:00", + "2022-01-20T14:00:00", + "2022-01-20T16:00:00", + "2022-01-20T18:00:00", + "2022-01-20T20:00:00", + "2022-01-20T22:00:00", + "2022-01-21T00:00:00", + "2022-01-21T02:00:00", + "2022-01-21T04:00:00", + "2022-01-21T06:00:00", + "2022-01-21T08:00:00", + "2022-01-21T10:00:00", + "2022-01-21T12:00:00", + "2022-01-21T14:00:00", + "2022-01-21T16:00:00", + "2022-01-21T18:00:00", + "2022-01-21T20:00:00", + "2022-01-21T22:00:00", + "2022-01-22T00:00:00", + "2022-01-22T02:00:00", + "2022-01-22T04:00:00", + "2022-01-22T06:00:00", + "2022-01-22T08:00:00", + "2022-01-22T10:00:00", + "2022-01-22T12:00:00", + "2022-01-22T14:00:00", + "2022-01-22T16:00:00", + "2022-01-22T18:00:00", + "2022-01-22T20:00:00", + "2022-01-22T22:00:00", + "2022-01-23T00:00:00", + "2022-01-23T02:00:00", + "2022-01-23T04:00:00", + "2022-01-23T06:00:00", + "2022-01-23T08:00:00", + "2022-01-23T10:00:00", + "2022-01-23T12:00:00", + "2022-01-23T14:00:00", + "2022-01-23T16:00:00", + "2022-01-23T18:00:00", + "2022-01-23T20:00:00", + "2022-01-23T22:00:00", + "2022-01-24T00:00:00", + "2022-01-24T02:00:00", + "2022-01-24T04:00:00", + "2022-01-24T06:00:00", + "2022-01-24T08:00:00", + "2022-01-24T10:00:00", + "2022-01-24T12:00:00", + "2022-01-24T14:00:00", + "2022-01-24T16:00:00", + "2022-01-24T18:00:00", + "2022-01-24T20:00:00", + "2022-01-24T22:00:00", + "2022-01-25T00:00:00", + "2022-01-25T02:00:00", + "2022-01-25T04:00:00", + "2022-01-25T06:00:00", + "2022-01-25T08:00:00", + "2022-01-25T10:00:00", + "2022-01-25T12:00:00", + "2022-01-25T14:00:00", + "2022-01-25T16:00:00", + "2022-01-25T18:00:00", + "2022-01-25T20:00:00", + "2022-01-25T22:00:00", + "2022-01-26T00:00:00", + "2022-01-26T02:00:00", + "2022-01-26T04:00:00", + "2022-01-26T06:00:00", + "2022-01-26T08:00:00", + "2022-01-26T10:00:00", + "2022-01-26T12:00:00", + "2022-01-26T14:00:00", + "2022-01-26T16:00:00", + "2022-01-26T18:00:00", + "2022-01-26T20:00:00", + "2022-01-26T22:00:00", + "2022-01-27T00:00:00", + "2022-01-27T02:00:00", + "2022-01-27T04:00:00", + "2022-01-27T06:00:00", + "2022-01-27T08:00:00", + "2022-01-27T10:00:00", + "2022-01-27T12:00:00", + "2022-01-27T14:00:00", + "2022-01-27T16:00:00", + "2022-01-27T18:00:00", + "2022-01-27T20:00:00", + "2022-01-27T22:00:00", + "2022-01-28T00:00:00", + "2022-01-28T02:00:00", + "2022-01-28T04:00:00", + "2022-01-28T06:00:00", + "2022-01-28T08:00:00", + "2022-01-28T10:00:00", + "2022-01-28T12:00:00", + "2022-01-28T14:00:00", + "2022-01-28T16:00:00", + "2022-01-28T18:00:00", + "2022-01-28T20:00:00", + "2022-01-28T22:00:00", + "2022-01-29T00:00:00", + "2022-01-29T02:00:00", + "2022-01-29T04:00:00", + "2022-01-29T06:00:00", + "2022-01-29T08:00:00", + "2022-01-29T10:00:00", + "2022-01-29T12:00:00", + "2022-01-29T14:00:00", + "2022-01-29T16:00:00", + "2022-01-29T18:00:00", + "2022-01-29T20:00:00", + "2022-01-29T22:00:00", + "2022-01-30T00:00:00", + "2022-01-30T02:00:00", + "2022-01-30T04:00:00", + "2022-01-30T06:00:00", + "2022-01-30T08:00:00", + "2022-01-30T10:00:00", + "2022-01-30T12:00:00", + "2022-01-30T14:00:00", + "2022-01-30T16:00:00", + "2022-01-30T18:00:00", + "2022-01-30T20:00:00", + "2022-01-30T22:00:00", + "2022-01-31T00:00:00", + "2022-01-31T02:00:00", + "2022-01-31T04:00:00", + "2022-01-31T06:00:00", + "2022-01-31T08:00:00", + "2022-01-31T10:00:00", + "2022-01-31T12:00:00", + "2022-01-31T14:00:00", + "2022-01-31T16:00:00", + "2022-01-31T18:00:00", + "2022-01-31T20:00:00", + "2022-01-31T22:00:00", + "2022-02-01T00:00:00", + "2022-02-01T02:00:00", + "2022-02-01T04:00:00", + "2022-02-01T06:00:00", + "2022-02-01T08:00:00", + "2022-02-01T10:00:00", + "2022-02-01T12:00:00", + "2022-02-01T14:00:00", + "2022-02-01T16:00:00", + "2022-02-01T18:00:00", + "2022-02-01T20:00:00", + "2022-02-01T22:00:00", + "2022-02-02T00:00:00", + "2022-02-02T02:00:00", + "2022-02-02T04:00:00", + "2022-02-02T06:00:00", + "2022-02-02T08:00:00", + "2022-02-02T10:00:00", + "2022-02-02T12:00:00", + "2022-02-02T14:00:00", + "2022-02-02T16:00:00", + "2022-02-02T18:00:00", + "2022-02-02T20:00:00", + "2022-02-02T22:00:00", + "2022-02-03T00:00:00", + "2022-02-03T02:00:00", + "2022-02-03T04:00:00", + "2022-02-03T06:00:00", + "2022-02-03T08:00:00", + "2022-02-03T10:00:00", + "2022-02-03T12:00:00", + "2022-02-03T14:00:00", + "2022-02-03T16:00:00", + "2022-02-03T18:00:00", + "2022-02-03T20:00:00", + "2022-02-03T22:00:00", + "2022-02-04T00:00:00", + "2022-02-04T02:00:00", + "2022-02-04T04:00:00", + "2022-02-04T06:00:00", + "2022-02-04T08:00:00", + "2022-02-04T10:00:00", + "2022-02-04T12:00:00", + "2022-02-04T14:00:00", + "2022-02-04T16:00:00", + "2022-02-04T18:00:00", + "2022-02-04T20:00:00", + "2022-02-04T22:00:00", + "2022-02-05T00:00:00", + "2022-02-05T02:00:00", + "2022-02-05T04:00:00", + "2022-02-05T06:00:00", + "2022-02-05T08:00:00", + "2022-02-05T10:00:00", + "2022-02-05T12:00:00", + "2022-02-05T14:00:00", + "2022-02-05T16:00:00", + "2022-02-05T18:00:00", + "2022-02-05T20:00:00", + "2022-02-05T22:00:00", + "2022-02-06T00:00:00", + "2022-02-06T02:00:00", + "2022-02-06T04:00:00", + "2022-02-06T06:00:00", + "2022-02-06T08:00:00", + "2022-02-06T10:00:00", + "2022-02-06T12:00:00", + "2022-02-06T14:00:00", + "2022-02-06T16:00:00", + "2022-02-06T18:00:00", + "2022-02-06T20:00:00", + "2022-02-06T22:00:00", + "2022-02-07T00:00:00", + "2022-02-07T02:00:00", + "2022-02-07T04:00:00", + "2022-02-07T06:00:00", + "2022-02-07T08:00:00", + "2022-02-07T10:00:00", + "2022-02-07T12:00:00", + "2022-02-07T14:00:00", + "2022-02-07T16:00:00", + "2022-02-07T18:00:00", + "2022-02-07T20:00:00", + "2022-02-07T22:00:00", + "2022-02-08T00:00:00", + "2022-02-08T02:00:00", + "2022-02-08T04:00:00", + "2022-02-08T06:00:00", + "2022-02-08T08:00:00", + "2022-02-08T10:00:00", + "2022-02-08T12:00:00", + "2022-02-08T14:00:00", + "2022-02-08T16:00:00", + "2022-02-08T18:00:00", + "2022-02-08T20:00:00", + "2022-02-08T22:00:00", + "2022-02-09T00:00:00", + "2022-02-09T02:00:00", + "2022-02-09T04:00:00", + "2022-02-09T06:00:00", + "2022-02-09T08:00:00", + "2022-02-09T10:00:00", + "2022-02-09T12:00:00", + "2022-02-09T14:00:00", + "2022-02-09T16:00:00", + "2022-02-09T18:00:00", + "2022-02-09T20:00:00", + "2022-02-09T22:00:00", + "2022-02-10T00:00:00", + "2022-02-10T02:00:00", + "2022-02-10T04:00:00", + "2022-02-10T06:00:00", + "2022-02-10T08:00:00", + "2022-02-10T10:00:00", + "2022-02-10T12:00:00", + "2022-02-10T14:00:00", + "2022-02-10T16:00:00", + "2022-02-10T18:00:00", + "2022-02-10T20:00:00", + "2022-02-10T22:00:00", + "2022-02-11T00:00:00", + "2022-02-11T02:00:00", + "2022-02-11T04:00:00", + "2022-02-11T06:00:00", + "2022-02-11T08:00:00", + "2022-02-11T10:00:00", + "2022-02-11T12:00:00", + "2022-02-11T14:00:00", + "2022-02-11T16:00:00", + "2022-02-11T18:00:00", + "2022-02-11T20:00:00", + "2022-02-11T22:00:00", + "2022-02-12T00:00:00", + "2022-02-12T02:00:00", + "2022-02-12T04:00:00", + "2022-02-12T06:00:00", + "2022-02-12T08:00:00", + "2022-02-12T10:00:00", + "2022-02-12T12:00:00", + "2022-02-12T14:00:00", + "2022-02-12T16:00:00", + "2022-02-12T18:00:00", + "2022-02-12T20:00:00", + "2022-02-12T22:00:00", + "2022-02-13T00:00:00", + "2022-02-13T02:00:00", + "2022-02-13T04:00:00", + "2022-02-13T06:00:00", + "2022-02-13T08:00:00", + "2022-02-13T10:00:00", + "2022-02-13T12:00:00", + "2022-02-13T14:00:00", + "2022-02-13T16:00:00", + "2022-02-13T18:00:00", + "2022-02-13T20:00:00", + "2022-02-13T22:00:00", + "2022-02-14T00:00:00", + "2022-02-14T02:00:00", + "2022-02-14T04:00:00", + "2022-02-14T06:00:00", + "2022-02-14T08:00:00", + "2022-02-14T10:00:00", + "2022-02-14T12:00:00", + "2022-02-14T14:00:00", + "2022-02-14T16:00:00", + "2022-02-14T18:00:00", + "2022-02-14T20:00:00", + "2022-02-14T22:00:00", + "2022-02-15T00:00:00", + "2022-02-15T02:00:00", + "2022-02-15T04:00:00", + "2022-02-15T06:00:00", + "2022-02-15T08:00:00", + "2022-02-15T10:00:00", + "2022-02-15T12:00:00", + "2022-02-15T14:00:00", + "2022-02-15T16:00:00", + "2022-02-15T18:00:00", + "2022-02-15T20:00:00", + "2022-02-15T22:00:00", + "2022-02-16T00:00:00", + "2022-02-16T02:00:00", + "2022-02-16T04:00:00", + "2022-02-16T06:00:00", + "2022-02-16T08:00:00", + "2022-02-16T10:00:00", + "2022-02-16T12:00:00", + "2022-02-16T14:00:00", + "2022-02-16T16:00:00", + "2022-02-16T18:00:00", + "2022-02-16T20:00:00", + "2022-02-16T22:00:00", + "2022-02-17T00:00:00", + "2022-02-17T02:00:00", + "2022-02-17T04:00:00", + "2022-02-17T06:00:00", + "2022-02-17T08:00:00", + "2022-02-17T10:00:00", + "2022-02-17T12:00:00", + "2022-02-17T14:00:00", + "2022-02-17T16:00:00", + "2022-02-17T18:00:00", + "2022-02-17T20:00:00", + "2022-02-17T22:00:00", + "2022-02-18T00:00:00", + "2022-02-18T02:00:00", + "2022-02-18T04:00:00", + "2022-02-18T06:00:00", + "2022-02-18T08:00:00", + "2022-02-18T10:00:00", + "2022-02-18T12:00:00", + "2022-02-18T14:00:00", + "2022-02-18T16:00:00", + "2022-02-18T18:00:00", + "2022-02-18T20:00:00", + "2022-02-18T22:00:00", + "2022-02-19T00:00:00", + "2022-02-19T02:00:00", + "2022-02-19T04:00:00", + "2022-02-19T06:00:00", + "2022-02-19T08:00:00", + "2022-02-19T10:00:00", + "2022-02-19T12:00:00", + "2022-02-19T14:00:00", + "2022-02-19T16:00:00", + "2022-02-19T18:00:00", + "2022-02-19T20:00:00", + "2022-02-19T22:00:00", + "2022-02-20T00:00:00", + "2022-02-20T02:00:00", + "2022-02-20T04:00:00", + "2022-02-20T06:00:00", + "2022-02-20T08:00:00", + "2022-02-20T10:00:00", + "2022-02-20T12:00:00", + "2022-02-20T14:00:00", + "2022-02-20T16:00:00", + "2022-02-20T18:00:00", + "2022-02-20T20:00:00", + "2022-02-20T22:00:00", + "2022-02-21T00:00:00", + "2022-02-21T02:00:00", + "2022-02-21T04:00:00", + "2022-02-21T06:00:00", + "2022-02-21T08:00:00", + "2022-02-21T10:00:00", + "2022-02-21T12:00:00", + "2022-02-21T14:00:00", + "2022-02-21T16:00:00", + "2022-02-21T18:00:00", + "2022-02-21T20:00:00", + "2022-02-21T22:00:00", + "2022-02-22T00:00:00", + "2022-02-22T02:00:00", + "2022-02-22T04:00:00", + "2022-02-22T06:00:00", + "2022-02-22T08:00:00", + "2022-02-22T10:00:00", + "2022-02-22T12:00:00", + "2022-02-22T14:00:00", + "2022-02-22T16:00:00", + "2022-02-22T18:00:00", + "2022-02-22T20:00:00", + "2022-02-22T22:00:00", + "2022-02-23T00:00:00", + "2022-02-23T02:00:00", + "2022-02-23T04:00:00", + "2022-02-23T06:00:00", + "2022-02-23T08:00:00", + "2022-02-23T10:00:00", + "2022-02-23T12:00:00", + "2022-02-23T14:00:00", + "2022-02-23T16:00:00", + "2022-02-23T18:00:00", + "2022-02-23T20:00:00", + "2022-02-23T22:00:00", + "2022-02-24T00:00:00", + "2022-02-24T02:00:00", + "2022-02-24T04:00:00", + "2022-02-24T06:00:00", + "2022-02-24T08:00:00", + "2022-02-24T10:00:00", + "2022-02-24T12:00:00", + "2022-02-24T14:00:00", + "2022-02-24T16:00:00", + "2022-02-24T18:00:00", + "2022-02-24T20:00:00", + "2022-02-24T22:00:00", + "2022-02-25T00:00:00", + "2022-02-25T02:00:00", + "2022-02-25T04:00:00", + "2022-02-25T06:00:00", + "2022-02-25T08:00:00", + "2022-02-25T10:00:00", + "2022-02-25T12:00:00", + "2022-02-25T14:00:00", + "2022-02-25T16:00:00", + "2022-02-25T18:00:00", + "2022-02-25T20:00:00", + "2022-02-25T22:00:00", + "2022-02-26T00:00:00", + "2022-02-26T02:00:00", + "2022-02-26T04:00:00", + "2022-02-26T06:00:00", + "2022-02-26T08:00:00", + "2022-02-26T10:00:00", + "2022-02-26T12:00:00", + "2022-02-26T14:00:00", + "2022-02-26T16:00:00", + "2022-02-26T18:00:00", + "2022-02-26T20:00:00", + "2022-02-26T22:00:00", + "2022-02-27T00:00:00", + "2022-02-27T02:00:00", + "2022-02-27T04:00:00", + "2022-02-27T06:00:00", + "2022-02-27T08:00:00", + "2022-02-27T10:00:00", + "2022-02-27T12:00:00", + "2022-02-27T14:00:00", + "2022-02-27T16:00:00", + "2022-02-27T18:00:00", + "2022-02-27T20:00:00", + "2022-02-27T22:00:00", + "2022-02-28T00:00:00", + "2022-02-28T02:00:00", + "2022-02-28T04:00:00", + "2022-02-28T06:00:00", + "2022-02-28T08:00:00", + "2022-02-28T10:00:00", + "2022-02-28T12:00:00", + "2022-02-28T14:00:00", + "2022-02-28T16:00:00", + "2022-02-28T18:00:00", + "2022-02-28T20:00:00", + "2022-02-28T22:00:00", + "2022-03-01T00:00:00", + "2022-03-01T02:00:00", + "2022-03-01T04:00:00", + "2022-03-01T06:00:00", + "2022-03-01T08:00:00", + "2022-03-01T10:00:00", + "2022-03-01T12:00:00", + "2022-03-01T14:00:00", + "2022-03-01T16:00:00", + "2022-03-01T18:00:00", + "2022-03-01T20:00:00", + "2022-03-01T22:00:00", + "2022-03-02T00:00:00", + "2022-03-02T02:00:00", + "2022-03-02T04:00:00", + "2022-03-02T06:00:00", + "2022-03-02T08:00:00", + "2022-03-02T10:00:00", + "2022-03-02T12:00:00", + "2022-03-02T14:00:00", + "2022-03-02T16:00:00", + "2022-03-02T18:00:00", + "2022-03-02T20:00:00", + "2022-03-02T22:00:00", + "2022-03-03T00:00:00", + "2022-03-03T02:00:00", + "2022-03-03T04:00:00", + "2022-03-03T06:00:00", + "2022-03-03T08:00:00", + "2022-03-03T10:00:00", + "2022-03-03T12:00:00", + "2022-03-03T14:00:00", + "2022-03-03T16:00:00", + "2022-03-03T18:00:00", + "2022-03-03T20:00:00", + "2022-03-03T22:00:00", + "2022-03-04T00:00:00", + "2022-03-04T02:00:00", + "2022-03-04T04:00:00", + "2022-03-04T06:00:00", + "2022-03-04T08:00:00", + "2022-03-04T10:00:00", + "2022-03-04T12:00:00", + "2022-03-04T14:00:00", + "2022-03-04T16:00:00", + "2022-03-04T18:00:00", + "2022-03-04T20:00:00", + "2022-03-04T22:00:00", + "2022-03-05T00:00:00", + "2022-03-05T02:00:00", + "2022-03-05T04:00:00", + "2022-03-05T06:00:00", + "2022-03-05T08:00:00", + "2022-03-05T10:00:00", + "2022-03-05T12:00:00", + "2022-03-05T14:00:00", + "2022-03-05T16:00:00", + "2022-03-05T18:00:00", + "2022-03-05T20:00:00", + "2022-03-05T22:00:00", + "2022-03-06T00:00:00", + "2022-03-06T02:00:00", + "2022-03-06T04:00:00", + "2022-03-06T06:00:00", + "2022-03-06T08:00:00", + "2022-03-06T10:00:00", + "2022-03-06T12:00:00", + "2022-03-06T14:00:00", + "2022-03-06T16:00:00", + "2022-03-06T18:00:00", + "2022-03-06T20:00:00", + "2022-03-06T22:00:00", + "2022-03-07T00:00:00", + "2022-03-07T02:00:00", + "2022-03-07T04:00:00", + "2022-03-07T06:00:00", + "2022-03-07T08:00:00", + "2022-03-07T10:00:00", + "2022-03-07T12:00:00", + "2022-03-07T14:00:00", + "2022-03-07T16:00:00", + "2022-03-07T18:00:00", + "2022-03-07T20:00:00", + "2022-03-07T22:00:00", + "2022-03-08T00:00:00", + "2022-03-08T02:00:00", + "2022-03-08T04:00:00", + "2022-03-08T06:00:00", + "2022-03-08T08:00:00", + "2022-03-08T10:00:00", + "2022-03-08T12:00:00", + "2022-03-08T14:00:00", + "2022-03-08T16:00:00", + "2022-03-08T18:00:00", + "2022-03-08T20:00:00", + "2022-03-08T22:00:00", + "2022-03-09T00:00:00", + "2022-03-09T02:00:00", + "2022-03-09T04:00:00", + "2022-03-09T06:00:00", + "2022-03-09T08:00:00", + "2022-03-09T10:00:00", + "2022-03-09T12:00:00", + "2022-03-09T14:00:00", + "2022-03-09T16:00:00", + "2022-03-09T18:00:00", + "2022-03-09T20:00:00", + "2022-03-09T22:00:00", + "2022-03-10T00:00:00", + "2022-03-10T02:00:00", + "2022-03-10T04:00:00", + "2022-03-10T06:00:00", + "2022-03-10T08:00:00", + "2022-03-10T10:00:00", + "2022-03-10T12:00:00", + "2022-03-10T14:00:00", + "2022-03-10T16:00:00", + "2022-03-10T18:00:00", + "2022-03-10T20:00:00", + "2022-03-10T22:00:00", + "2022-03-11T00:00:00", + "2022-03-11T02:00:00", + "2022-03-11T04:00:00", + "2022-03-11T06:00:00", + "2022-03-11T08:00:00", + "2022-03-11T10:00:00", + "2022-03-11T12:00:00", + "2022-03-11T14:00:00", + "2022-03-11T16:00:00", + "2022-03-11T18:00:00", + "2022-03-11T20:00:00", + "2022-03-11T22:00:00", + "2022-03-12T00:00:00", + "2022-03-12T02:00:00", + "2022-03-12T04:00:00", + "2022-03-12T06:00:00", + "2022-03-12T08:00:00", + "2022-03-12T10:00:00", + "2022-03-12T12:00:00", + "2022-03-12T14:00:00", + "2022-03-12T16:00:00", + "2022-03-12T18:00:00", + "2022-03-12T20:00:00", + "2022-03-12T22:00:00", + "2022-03-13T00:00:00", + "2022-03-13T02:00:00", + "2022-03-13T04:00:00", + "2022-03-13T06:00:00", + "2022-03-13T08:00:00", + "2022-03-13T10:00:00", + "2022-03-13T12:00:00", + "2022-03-13T14:00:00", + "2022-03-13T16:00:00", + "2022-03-13T18:00:00", + "2022-03-13T20:00:00", + "2022-03-13T22:00:00", + "2022-03-14T00:00:00", + "2022-03-14T02:00:00", + "2022-03-14T04:00:00", + "2022-03-14T06:00:00", + "2022-03-14T08:00:00", + "2022-03-14T10:00:00", + "2022-03-14T12:00:00", + "2022-03-14T14:00:00", + "2022-03-14T16:00:00", + "2022-03-14T18:00:00", + "2022-03-14T20:00:00", + "2022-03-14T22:00:00", + "2022-03-15T00:00:00", + "2022-03-15T02:00:00", + "2022-03-15T04:00:00", + "2022-03-15T06:00:00", + "2022-03-15T08:00:00", + "2022-03-15T10:00:00", + "2022-03-15T12:00:00", + "2022-03-15T14:00:00", + "2022-03-15T16:00:00", + "2022-03-15T18:00:00", + "2022-03-15T20:00:00", + "2022-03-15T22:00:00", + "2022-03-16T00:00:00", + "2022-03-16T02:00:00", + "2022-03-16T04:00:00", + "2022-03-16T06:00:00", + "2022-03-16T08:00:00", + "2022-03-16T10:00:00", + "2022-03-16T12:00:00", + "2022-03-16T14:00:00", + "2022-03-16T16:00:00", + "2022-03-16T18:00:00", + "2022-03-16T20:00:00", + "2022-03-16T22:00:00", + "2022-03-17T00:00:00", + "2022-03-17T02:00:00", + "2022-03-17T04:00:00", + "2022-03-17T06:00:00", + "2022-03-17T08:00:00", + "2022-03-17T10:00:00", + "2022-03-17T12:00:00", + "2022-03-17T14:00:00", + "2022-03-17T16:00:00", + "2022-03-17T18:00:00", + "2022-03-17T20:00:00", + "2022-03-17T22:00:00", + "2022-03-18T00:00:00", + "2022-03-18T02:00:00", + "2022-03-18T04:00:00", + "2022-03-18T06:00:00", + "2022-03-18T08:00:00", + "2022-03-18T10:00:00", + "2022-03-18T12:00:00", + "2022-03-18T14:00:00", + "2022-03-18T16:00:00", + "2022-03-18T18:00:00", + "2022-03-18T20:00:00", + "2022-03-18T22:00:00", + "2022-03-19T00:00:00", + "2022-03-19T02:00:00", + "2022-03-19T04:00:00", + "2022-03-19T06:00:00", + "2022-03-19T08:00:00", + "2022-03-19T10:00:00", + "2022-03-19T12:00:00", + "2022-03-19T14:00:00", + "2022-03-19T16:00:00", + "2022-03-19T18:00:00", + "2022-03-19T20:00:00", + "2022-03-19T22:00:00", + "2022-03-20T00:00:00", + "2022-03-20T02:00:00", + "2022-03-20T04:00:00", + "2022-03-20T06:00:00", + "2022-03-20T08:00:00", + "2022-03-20T10:00:00", + "2022-03-20T12:00:00", + "2022-03-20T14:00:00", + "2022-03-20T16:00:00", + "2022-03-20T18:00:00", + "2022-03-20T20:00:00", + "2022-03-20T22:00:00", + "2022-03-21T00:00:00", + "2022-03-21T02:00:00", + "2022-03-21T04:00:00", + "2022-03-21T06:00:00", + "2022-03-21T08:00:00", + "2022-03-21T10:00:00", + "2022-03-21T12:00:00", + "2022-03-21T14:00:00", + "2022-03-21T16:00:00", + "2022-03-21T18:00:00", + "2022-03-21T20:00:00", + "2022-03-21T22:00:00", + "2022-03-22T00:00:00", + "2022-03-22T02:00:00", + "2022-03-22T04:00:00", + "2022-03-22T06:00:00", + "2022-03-22T08:00:00", + "2022-03-22T10:00:00", + "2022-03-22T12:00:00", + "2022-03-22T14:00:00", + "2022-03-22T16:00:00", + "2022-03-22T18:00:00", + "2022-03-22T20:00:00", + "2022-03-22T22:00:00", + "2022-03-23T00:00:00", + "2022-03-23T02:00:00", + "2022-03-23T04:00:00", + "2022-03-23T06:00:00", + "2022-03-23T08:00:00", + "2022-03-23T10:00:00", + "2022-03-23T12:00:00", + "2022-03-23T14:00:00", + "2022-03-23T16:00:00", + "2022-03-23T18:00:00", + "2022-03-23T20:00:00", + "2022-03-23T22:00:00", + "2022-03-24T00:00:00", + "2022-03-24T02:00:00", + "2022-03-24T04:00:00", + "2022-03-24T06:00:00", + "2022-03-24T08:00:00", + "2022-03-24T10:00:00", + "2022-03-24T12:00:00", + "2022-03-24T14:00:00", + "2022-03-24T16:00:00", + "2022-03-24T18:00:00", + "2022-03-24T20:00:00", + "2022-03-24T22:00:00", + "2022-03-25T00:00:00", + "2022-03-25T02:00:00", + "2022-03-25T04:00:00", + "2022-03-25T06:00:00", + "2022-03-25T08:00:00", + "2022-03-25T10:00:00", + "2022-03-25T12:00:00", + "2022-03-25T14:00:00", + "2022-03-25T16:00:00", + "2022-03-25T18:00:00", + "2022-03-25T20:00:00", + "2022-03-25T22:00:00", + "2022-03-26T00:00:00", + "2022-03-26T02:00:00", + "2022-03-26T04:00:00", + "2022-03-26T06:00:00", + "2022-03-26T08:00:00", + "2022-03-26T10:00:00", + "2022-03-26T12:00:00", + "2022-03-26T14:00:00", + "2022-03-26T16:00:00", + "2022-03-26T18:00:00", + "2022-03-26T20:00:00", + "2022-03-26T22:00:00", + "2022-03-27T00:00:00", + "2022-03-27T02:00:00", + "2022-03-27T04:00:00", + "2022-03-27T06:00:00", + "2022-03-27T08:00:00", + "2022-03-27T10:00:00", + "2022-03-27T12:00:00", + "2022-03-27T14:00:00", + "2022-03-27T16:00:00", + "2022-03-27T18:00:00", + "2022-03-27T20:00:00", + "2022-03-27T22:00:00", + "2022-03-28T00:00:00", + "2022-03-28T02:00:00", + "2022-03-28T04:00:00", + "2022-03-28T06:00:00", + "2022-03-28T08:00:00", + "2022-03-28T10:00:00", + "2022-03-28T12:00:00", + "2022-03-28T14:00:00", + "2022-03-28T16:00:00", + "2022-03-28T18:00:00", + "2022-03-28T20:00:00", + "2022-03-28T22:00:00", + "2022-03-29T00:00:00", + "2022-03-29T02:00:00", + "2022-03-29T04:00:00", + "2022-03-29T06:00:00", + "2022-03-29T08:00:00", + "2022-03-29T10:00:00", + "2022-03-29T12:00:00", + "2022-03-29T14:00:00", + "2022-03-29T16:00:00", + "2022-03-29T18:00:00", + "2022-03-29T20:00:00", + "2022-03-29T22:00:00", + "2022-03-30T00:00:00", + "2022-03-30T02:00:00", + "2022-03-30T04:00:00", + "2022-03-30T06:00:00", + "2022-03-30T08:00:00", + "2022-03-30T10:00:00", + "2022-03-30T12:00:00", + "2022-03-30T14:00:00", + "2022-03-30T16:00:00", + "2022-03-30T18:00:00", + "2022-03-30T20:00:00", + "2022-03-30T22:00:00", + "2022-03-31T00:00:00", + "2022-03-31T02:00:00", + "2022-03-31T04:00:00", + "2022-03-31T06:00:00", + "2022-03-31T08:00:00", + "2022-03-31T10:00:00", + "2022-03-31T12:00:00", + "2022-03-31T14:00:00", + "2022-03-31T16:00:00", + "2022-03-31T18:00:00", + "2022-03-31T20:00:00", + "2022-03-31T22:00:00", + "2022-04-01T00:00:00", + "2022-04-01T02:00:00", + "2022-04-01T04:00:00", + "2022-04-01T06:00:00", + "2022-04-01T08:00:00", + "2022-04-01T10:00:00", + "2022-04-01T12:00:00", + "2022-04-01T14:00:00", + "2022-04-01T16:00:00", + "2022-04-01T18:00:00", + "2022-04-01T20:00:00", + "2022-04-01T22:00:00", + "2022-04-02T00:00:00", + "2022-04-02T02:00:00", + "2022-04-02T04:00:00", + "2022-04-02T06:00:00", + "2022-04-02T08:00:00", + "2022-04-02T10:00:00", + "2022-04-02T12:00:00", + "2022-04-02T14:00:00", + "2022-04-02T16:00:00", + "2022-04-02T18:00:00", + "2022-04-02T20:00:00", + "2022-04-02T22:00:00", + "2022-04-03T00:00:00", + "2022-04-03T02:00:00", + "2022-04-03T04:00:00", + "2022-04-03T06:00:00", + "2022-04-03T08:00:00", + "2022-04-03T10:00:00", + "2022-04-03T12:00:00", + "2022-04-03T14:00:00", + "2022-04-03T16:00:00", + "2022-04-03T18:00:00", + "2022-04-03T20:00:00", + "2022-04-03T22:00:00", + "2022-04-04T00:00:00", + "2022-04-04T02:00:00", + "2022-04-04T04:00:00", + "2022-04-04T06:00:00", + "2022-04-04T08:00:00", + "2022-04-04T10:00:00", + "2022-04-04T12:00:00", + "2022-04-04T14:00:00", + "2022-04-04T16:00:00", + "2022-04-04T18:00:00", + "2022-04-04T20:00:00", + "2022-04-04T22:00:00", + "2022-04-05T00:00:00", + "2022-04-05T02:00:00", + "2022-04-05T04:00:00", + "2022-04-05T06:00:00", + "2022-04-05T08:00:00", + "2022-04-05T10:00:00", + "2022-04-05T12:00:00", + "2022-04-05T14:00:00", + "2022-04-05T16:00:00", + "2022-04-05T18:00:00", + "2022-04-05T20:00:00", + "2022-04-05T22:00:00", + "2022-04-06T00:00:00", + "2022-04-06T02:00:00", + "2022-04-06T04:00:00", + "2022-04-06T06:00:00", + "2022-04-06T08:00:00", + "2022-04-06T10:00:00", + "2022-04-06T12:00:00", + "2022-04-06T14:00:00", + "2022-04-06T16:00:00", + "2022-04-06T18:00:00", + "2022-04-06T20:00:00", + "2022-04-06T22:00:00", + "2022-04-07T00:00:00", + "2022-04-07T02:00:00", + "2022-04-07T04:00:00", + "2022-04-07T06:00:00", + "2022-04-07T08:00:00", + "2022-04-07T10:00:00", + "2022-04-07T12:00:00", + "2022-04-07T14:00:00", + "2022-04-07T16:00:00", + "2022-04-07T18:00:00", + "2022-04-07T20:00:00", + "2022-04-07T22:00:00", + "2022-04-08T00:00:00", + "2022-04-08T02:00:00", + "2022-04-08T04:00:00", + "2022-04-08T06:00:00", + "2022-04-08T08:00:00", + "2022-04-08T10:00:00", + "2022-04-08T12:00:00", + "2022-04-08T14:00:00", + "2022-04-08T16:00:00", + "2022-04-08T18:00:00", + "2022-04-08T20:00:00", + "2022-04-08T22:00:00", + "2022-04-09T00:00:00", + "2022-04-09T02:00:00", + "2022-04-09T04:00:00", + "2022-04-09T06:00:00", + "2022-04-09T08:00:00", + "2022-04-09T10:00:00", + "2022-04-09T12:00:00", + "2022-04-09T14:00:00", + "2022-04-09T16:00:00", + "2022-04-09T18:00:00", + "2022-04-09T20:00:00", + "2022-04-09T22:00:00", + "2022-04-10T00:00:00", + "2022-04-10T02:00:00", + "2022-04-10T04:00:00", + "2022-04-10T06:00:00", + "2022-04-10T08:00:00", + "2022-04-10T10:00:00", + "2022-04-10T12:00:00", + "2022-04-10T14:00:00", + "2022-04-10T16:00:00", + "2022-04-10T18:00:00", + "2022-04-10T20:00:00", + "2022-04-10T22:00:00", + "2022-04-11T00:00:00", + "2022-04-11T02:00:00", + "2022-04-11T04:00:00", + "2022-04-11T06:00:00", + "2022-04-11T08:00:00", + "2022-04-11T10:00:00", + "2022-04-11T12:00:00", + "2022-04-11T14:00:00", + "2022-04-11T16:00:00", + "2022-04-11T18:00:00", + "2022-04-11T20:00:00", + "2022-04-11T22:00:00", + "2022-04-12T00:00:00", + "2022-04-12T02:00:00", + "2022-04-12T04:00:00", + "2022-04-12T06:00:00", + "2022-04-12T08:00:00", + "2022-04-12T10:00:00", + "2022-04-12T12:00:00", + "2022-04-12T14:00:00", + "2022-04-12T16:00:00", + "2022-04-12T18:00:00", + "2022-04-12T20:00:00", + "2022-04-12T22:00:00", + "2022-04-13T00:00:00", + "2022-04-13T02:00:00", + "2022-04-13T04:00:00", + "2022-04-13T06:00:00", + "2022-04-13T08:00:00", + "2022-04-13T10:00:00", + "2022-04-13T12:00:00", + "2022-04-13T14:00:00", + "2022-04-13T16:00:00", + "2022-04-13T18:00:00", + "2022-04-13T20:00:00", + "2022-04-13T22:00:00", + "2022-04-14T00:00:00", + "2022-04-14T02:00:00", + "2022-04-14T04:00:00", + "2022-04-14T06:00:00", + "2022-04-14T08:00:00", + "2022-04-14T10:00:00", + "2022-04-14T12:00:00", + "2022-04-14T14:00:00", + "2022-04-14T16:00:00", + "2022-04-14T18:00:00", + "2022-04-14T20:00:00", + "2022-04-14T22:00:00", + "2022-04-15T00:00:00", + "2022-04-15T02:00:00", + "2022-04-15T04:00:00", + "2022-04-15T06:00:00", + "2022-04-15T08:00:00", + "2022-04-15T10:00:00", + "2022-04-15T12:00:00", + "2022-04-15T14:00:00", + "2022-04-15T16:00:00", + "2022-04-15T18:00:00", + "2022-04-15T20:00:00", + "2022-04-15T22:00:00", + "2022-04-16T00:00:00", + "2022-04-16T02:00:00", + "2022-04-16T04:00:00", + "2022-04-16T06:00:00", + "2022-04-16T08:00:00", + "2022-04-16T10:00:00", + "2022-04-16T12:00:00", + "2022-04-16T14:00:00", + "2022-04-16T16:00:00", + "2022-04-16T18:00:00", + "2022-04-16T20:00:00", + "2022-04-16T22:00:00", + "2022-04-17T00:00:00", + "2022-04-17T02:00:00", + "2022-04-17T04:00:00", + "2022-04-17T06:00:00", + "2022-04-17T08:00:00", + "2022-04-17T10:00:00", + "2022-04-17T12:00:00", + "2022-04-17T14:00:00", + "2022-04-17T16:00:00", + "2022-04-17T18:00:00", + "2022-04-17T20:00:00", + "2022-04-17T22:00:00", + "2022-04-18T00:00:00", + "2022-04-18T02:00:00", + "2022-04-18T04:00:00", + "2022-04-18T06:00:00", + "2022-04-18T08:00:00", + "2022-04-18T10:00:00", + "2022-04-18T12:00:00", + "2022-04-18T14:00:00", + "2022-04-18T16:00:00", + "2022-04-18T18:00:00", + "2022-04-18T20:00:00", + "2022-04-18T22:00:00", + "2022-04-19T00:00:00", + "2022-04-19T02:00:00", + "2022-04-19T04:00:00", + "2022-04-19T06:00:00", + "2022-04-19T08:00:00", + "2022-04-19T10:00:00", + "2022-04-19T12:00:00", + "2022-04-19T14:00:00", + "2022-04-19T16:00:00", + "2022-04-19T18:00:00", + "2022-04-19T20:00:00", + "2022-04-19T22:00:00", + "2022-04-20T00:00:00", + "2022-04-20T04:00:00", + "2022-04-20T06:00:00", + "2022-04-20T08:00:00", + "2022-04-20T10:00:00", + "2022-04-20T12:00:00", + "2022-04-20T14:00:00", + "2022-04-20T16:00:00", + "2022-04-20T18:00:00", + "2022-04-20T20:00:00", + "2022-04-20T22:00:00", + "2022-04-21T00:00:00", + "2022-04-21T02:00:00", + "2022-04-21T04:00:00", + "2022-04-21T06:00:00", + "2022-04-21T08:00:00", + "2022-04-21T10:00:00", + "2022-04-21T12:00:00", + "2022-04-21T14:00:00", + "2022-04-21T16:00:00", + "2022-04-21T18:00:00", + "2022-04-21T20:00:00", + "2022-04-21T22:00:00", + "2022-04-22T00:00:00", + "2022-04-22T02:00:00", + "2022-04-22T04:00:00", + "2022-04-22T06:00:00", + "2022-04-22T08:00:00", + "2022-04-22T10:00:00", + "2022-04-22T12:00:00", + "2022-04-22T14:00:00", + "2022-04-22T16:00:00", + "2022-04-22T18:00:00", + "2022-04-22T20:00:00", + "2022-04-22T22:00:00", + "2022-04-23T00:00:00", + "2022-04-23T02:00:00", + "2022-04-23T04:00:00", + "2022-04-23T06:00:00", + "2022-04-23T08:00:00", + "2022-04-23T10:00:00", + "2022-04-23T12:00:00", + "2022-04-23T14:00:00", + "2022-04-23T16:00:00", + "2022-04-23T18:00:00", + "2022-04-23T20:00:00", + "2022-04-23T22:00:00", + "2022-04-24T00:00:00", + "2022-04-24T02:00:00", + "2022-04-24T04:00:00", + "2022-04-24T06:00:00", + "2022-04-24T08:00:00", + "2022-04-24T10:00:00", + "2022-04-24T12:00:00", + "2022-04-24T14:00:00", + "2022-04-24T16:00:00", + "2022-04-24T18:00:00", + "2022-04-24T20:00:00", + "2022-04-24T22:00:00", + "2022-04-25T00:00:00", + "2022-04-25T02:00:00", + "2022-04-25T04:00:00", + "2022-04-25T06:00:00", + "2022-04-25T08:00:00", + "2022-04-25T10:00:00", + "2022-04-25T12:00:00", + "2022-04-25T14:00:00", + "2022-04-25T16:00:00", + "2022-04-25T18:00:00", + "2022-04-25T20:00:00", + "2022-04-25T22:00:00", + "2022-04-26T00:00:00", + "2022-04-26T02:00:00", + "2022-04-26T04:00:00", + "2022-04-26T06:00:00", + "2022-04-26T08:00:00", + "2022-04-26T10:00:00", + "2022-04-26T12:00:00", + "2022-04-26T14:00:00", + "2022-04-26T16:00:00", + "2022-04-26T18:00:00", + "2022-04-26T20:00:00", + "2022-04-26T22:00:00", + "2022-04-27T00:00:00", + "2022-04-27T02:00:00", + "2022-04-27T04:00:00", + "2022-04-27T06:00:00", + "2022-04-27T08:00:00", + "2022-04-27T10:00:00", + "2022-04-27T12:00:00", + "2022-04-27T14:00:00", + "2022-04-27T16:00:00", + "2022-04-27T18:00:00", + "2022-04-27T20:00:00", + "2022-04-27T22:00:00", + "2022-04-28T00:00:00", + "2022-04-28T02:00:00", + "2022-04-28T04:00:00", + "2022-04-28T06:00:00", + "2022-04-28T08:00:00", + "2022-04-28T10:00:00", + "2022-04-28T12:00:00", + "2022-04-28T14:00:00", + "2022-04-28T16:00:00", + "2022-04-28T18:00:00", + "2022-04-28T20:00:00", + "2022-04-28T22:00:00", + "2022-04-29T00:00:00", + "2022-04-29T02:00:00", + "2022-04-29T04:00:00", + "2022-04-29T06:00:00", + "2022-04-29T08:00:00", + "2022-04-29T10:00:00", + "2022-04-29T12:00:00", + "2022-04-29T14:00:00", + "2022-04-29T16:00:00", + "2022-04-29T18:00:00", + "2022-04-29T20:00:00", + "2022-04-29T22:00:00", + "2022-04-30T00:00:00", + "2022-04-30T02:00:00", + "2022-04-30T04:00:00", + "2022-04-30T06:00:00", + "2022-04-30T08:00:00", + "2022-04-30T10:00:00", + "2022-04-30T12:00:00", + "2022-04-30T14:00:00", + "2022-04-30T16:00:00", + "2022-04-30T18:00:00", + "2022-04-30T20:00:00", + "2022-04-30T22:00:00", + "2022-05-01T00:00:00", + "2022-05-01T02:00:00", + "2022-05-01T04:00:00", + "2022-05-01T06:00:00", + "2022-05-01T08:00:00", + "2022-05-01T10:00:00", + "2022-05-01T12:00:00", + "2022-05-01T14:00:00", + "2022-05-01T16:00:00", + "2022-05-01T18:00:00", + "2022-05-01T20:00:00", + "2022-05-01T22:00:00", + "2022-05-02T00:00:00", + "2022-05-02T02:00:00", + "2022-05-02T04:00:00", + "2022-05-02T06:00:00", + "2022-05-02T08:00:00", + "2022-05-02T10:00:00", + "2022-05-02T12:00:00", + "2022-05-02T14:00:00", + "2022-05-02T16:00:00", + "2022-05-02T18:00:00", + "2022-05-02T20:00:00", + "2022-05-02T22:00:00", + "2022-05-03T00:00:00", + "2022-05-03T02:00:00", + "2022-05-03T04:00:00", + "2022-05-03T06:00:00", + "2022-05-03T08:00:00", + "2022-05-03T10:00:00", + "2022-05-03T12:00:00", + "2022-05-03T14:00:00", + "2022-05-03T16:00:00", + "2022-05-03T18:00:00", + "2022-05-03T20:00:00", + "2022-05-03T22:00:00", + "2022-05-04T00:00:00", + "2022-05-04T02:00:00", + "2022-05-04T04:00:00", + "2022-05-04T06:00:00", + "2022-05-04T08:00:00", + "2022-05-04T10:00:00", + "2022-05-04T12:00:00", + "2022-05-04T14:00:00", + "2022-05-04T16:00:00", + "2022-05-04T18:00:00", + "2022-05-04T20:00:00", + "2022-05-04T22:00:00", + "2022-05-05T00:00:00", + "2022-05-05T02:00:00", + "2022-05-05T04:00:00", + "2022-05-05T06:00:00", + "2022-05-05T08:00:00", + "2022-05-05T10:00:00", + "2022-05-05T12:00:00", + "2022-05-05T14:00:00", + "2022-05-05T16:00:00", + "2022-05-05T18:00:00", + "2022-05-05T20:00:00", + "2022-05-05T22:00:00", + "2022-05-06T00:00:00", + "2022-05-06T02:00:00", + "2022-05-06T04:00:00", + "2022-05-06T06:00:00", + "2022-05-06T08:00:00", + "2022-05-06T10:00:00", + "2022-05-06T12:00:00", + "2022-05-06T14:00:00", + "2022-05-06T16:00:00", + "2022-05-06T18:00:00", + "2022-05-06T20:00:00", + "2022-05-06T22:00:00", + "2022-05-07T00:00:00", + "2022-05-07T02:00:00", + "2022-05-07T04:00:00", + "2022-05-07T06:00:00", + "2022-05-07T08:00:00", + "2022-05-07T10:00:00", + "2022-05-07T12:00:00", + "2022-05-07T14:00:00", + "2022-05-07T16:00:00", + "2022-05-07T18:00:00", + "2022-05-07T20:00:00", + "2022-05-07T22:00:00", + "2022-05-08T00:00:00", + "2022-05-08T02:00:00", + "2022-05-08T04:00:00", + "2022-05-08T06:00:00", + "2022-05-08T08:00:00", + "2022-05-08T10:00:00", + "2022-05-08T12:00:00", + "2022-05-08T14:00:00", + "2022-05-08T16:00:00", + "2022-05-08T18:00:00", + "2022-05-08T20:00:00", + "2022-05-08T22:00:00", + "2022-05-09T00:00:00", + "2022-05-09T02:00:00", + "2022-05-09T04:00:00", + "2022-05-09T06:00:00", + "2022-05-09T08:00:00", + "2022-05-09T10:00:00", + "2022-05-09T12:00:00", + "2022-05-09T14:00:00", + "2022-05-09T16:00:00", + "2022-05-09T18:00:00", + "2022-05-09T20:00:00", + "2022-05-09T22:00:00", + "2022-05-10T00:00:00", + "2022-05-10T02:00:00", + "2022-05-10T04:00:00", + "2022-05-10T06:00:00", + "2022-05-10T08:00:00", + "2022-05-10T10:00:00", + "2022-05-10T12:00:00", + "2022-05-10T14:00:00", + "2022-05-10T16:00:00", + "2022-05-10T18:00:00", + "2022-05-10T20:00:00", + "2022-05-10T22:00:00", + "2022-05-11T00:00:00", + "2022-05-11T02:00:00", + "2022-05-11T04:00:00", + "2022-05-11T06:00:00", + "2022-05-11T08:00:00", + "2022-05-11T10:00:00", + "2022-05-11T12:00:00", + "2022-05-11T14:00:00", + "2022-05-11T16:00:00", + "2022-05-11T18:00:00", + "2022-05-11T20:00:00", + "2022-05-11T22:00:00", + "2022-05-12T00:00:00", + "2022-05-12T02:00:00", + "2022-05-12T04:00:00", + "2022-05-12T06:00:00", + "2022-05-12T08:00:00", + "2022-05-12T10:00:00", + "2022-05-12T12:00:00", + "2022-05-12T14:00:00", + "2022-05-12T16:00:00", + "2022-05-12T18:00:00", + "2022-05-12T20:00:00", + "2022-05-12T22:00:00", + "2022-05-13T00:00:00", + "2022-05-13T02:00:00", + "2022-05-13T04:00:00", + "2022-05-13T06:00:00", + "2022-05-13T08:00:00", + "2022-05-13T10:00:00", + "2022-05-13T12:00:00", + "2022-05-13T14:00:00", + "2022-05-13T16:00:00", + "2022-05-13T18:00:00", + "2022-05-13T20:00:00", + "2022-05-13T22:00:00", + "2022-05-14T00:00:00", + "2022-05-14T02:00:00", + "2022-05-14T04:00:00", + "2022-05-14T06:00:00", + "2022-05-14T08:00:00", + "2022-05-14T10:00:00", + "2022-05-14T12:00:00", + "2022-05-14T14:00:00", + "2022-05-14T16:00:00", + "2022-05-14T18:00:00", + "2022-05-14T20:00:00", + "2022-05-14T22:00:00", + "2022-05-15T00:00:00", + "2022-05-15T02:00:00", + "2022-05-15T04:00:00", + "2022-05-15T06:00:00", + "2022-05-15T08:00:00", + "2022-05-15T10:00:00", + "2022-05-15T12:00:00", + "2022-05-15T14:00:00", + "2022-05-15T16:00:00", + "2022-05-15T18:00:00", + "2022-05-15T20:00:00", + "2022-05-15T22:00:00", + "2022-05-16T00:00:00", + "2022-05-16T02:00:00", + "2022-05-16T04:00:00", + "2022-05-16T06:00:00", + "2022-05-16T08:00:00", + "2022-05-16T10:00:00", + "2022-05-16T12:00:00", + "2022-05-16T14:00:00", + "2022-05-16T16:00:00", + "2022-05-16T18:00:00", + "2022-05-16T20:00:00", + "2022-05-16T22:00:00", + "2022-05-17T00:00:00", + "2022-05-17T02:00:00", + "2022-05-17T04:00:00", + "2022-05-17T06:00:00", + "2022-05-17T08:00:00", + "2022-05-17T10:00:00", + "2022-05-17T12:00:00", + "2022-05-17T14:00:00", + "2022-05-17T16:00:00", + "2022-05-17T18:00:00", + "2022-05-17T20:00:00", + "2022-05-17T22:00:00", + "2022-05-18T00:00:00", + "2022-05-18T02:00:00", + "2022-05-18T04:00:00", + "2022-05-18T06:00:00", + "2022-05-18T08:00:00", + "2022-05-18T10:00:00", + "2022-05-18T12:00:00", + "2022-05-18T14:00:00", + "2022-05-18T16:00:00", + "2022-05-18T18:00:00", + "2022-05-18T20:00:00", + "2022-05-18T22:00:00", + "2022-05-19T00:00:00", + "2022-05-19T02:00:00", + "2022-05-19T04:00:00", + "2022-05-19T06:00:00", + "2022-05-19T08:00:00", + "2022-05-19T10:00:00", + "2022-05-19T12:00:00", + "2022-05-19T14:00:00", + "2022-05-19T16:00:00", + "2022-05-19T18:00:00", + "2022-05-19T20:00:00", + "2022-05-19T22:00:00", + "2022-05-20T00:00:00", + "2022-05-20T02:00:00", + "2022-05-20T04:00:00", + "2022-05-20T06:00:00", + "2022-05-20T08:00:00", + "2022-05-20T10:00:00", + "2022-05-20T12:00:00", + "2022-05-20T14:00:00", + "2022-05-20T16:00:00", + "2022-05-20T18:00:00", + "2022-05-20T20:00:00", + "2022-05-20T22:00:00", + "2022-05-21T00:00:00", + "2022-05-21T02:00:00", + "2022-05-21T04:00:00", + "2022-05-21T06:00:00", + "2022-05-21T08:00:00", + "2022-05-21T10:00:00", + "2022-05-21T12:00:00", + "2022-05-21T14:00:00", + "2022-05-21T16:00:00", + "2022-05-21T18:00:00", + "2022-05-21T20:00:00", + "2022-05-21T22:00:00", + "2022-05-22T00:00:00", + "2022-05-22T02:00:00", + "2022-05-22T04:00:00", + "2022-05-22T06:00:00", + "2022-05-22T08:00:00", + "2022-05-22T10:00:00", + "2022-05-22T12:00:00", + "2022-05-22T14:00:00", + "2022-05-22T16:00:00", + "2022-05-22T18:00:00", + "2022-05-22T20:00:00", + "2022-05-22T22:00:00", + "2022-05-23T00:00:00", + "2022-05-23T02:00:00", + "2022-05-23T04:00:00", + "2022-05-23T06:00:00", + "2022-05-23T08:00:00", + "2022-05-23T10:00:00", + "2022-05-23T12:00:00", + "2022-05-23T14:00:00", + "2022-05-23T16:00:00", + "2022-05-23T18:00:00", + "2022-05-23T20:00:00", + "2022-05-23T22:00:00", + "2022-05-24T00:00:00", + "2022-05-24T02:00:00", + "2022-05-24T04:00:00", + "2022-05-24T06:00:00", + "2022-05-24T08:00:00", + "2022-05-24T10:00:00", + "2022-05-24T12:00:00", + "2022-05-24T14:00:00", + "2022-05-24T16:00:00", + "2022-05-24T18:00:00", + "2022-05-24T20:00:00", + "2022-05-24T22:00:00", + "2022-05-25T00:00:00", + "2022-05-25T02:00:00", + "2022-05-25T04:00:00", + "2022-05-25T06:00:00", + "2022-05-25T08:00:00", + "2022-05-25T10:00:00", + "2022-05-25T12:00:00", + "2022-05-25T14:00:00", + "2022-05-25T16:00:00", + "2022-05-25T18:00:00", + "2022-05-25T20:00:00", + "2022-05-25T22:00:00", + "2022-05-26T00:00:00", + "2022-05-26T02:00:00", + "2022-05-26T04:00:00", + "2022-05-26T06:00:00", + "2022-05-26T08:00:00", + "2022-05-26T10:00:00", + "2022-05-26T12:00:00", + "2022-05-26T14:00:00", + "2022-05-26T16:00:00", + "2022-05-26T18:00:00", + "2022-05-26T20:00:00", + "2022-05-26T22:00:00", + "2022-05-27T00:00:00", + "2022-05-27T02:00:00", + "2022-05-27T04:00:00", + "2022-05-27T06:00:00", + "2022-05-27T08:00:00", + "2022-05-27T10:00:00", + "2022-05-27T12:00:00", + "2022-05-27T14:00:00", + "2022-05-27T16:00:00", + "2022-05-27T18:00:00", + "2022-05-27T20:00:00", + "2022-05-27T22:00:00", + "2022-05-28T00:00:00", + "2022-05-28T02:00:00", + "2022-05-28T04:00:00", + "2022-05-28T06:00:00", + "2022-05-28T08:00:00", + "2022-05-28T10:00:00", + "2022-05-28T12:00:00", + "2022-05-28T14:00:00", + "2022-05-28T16:00:00", + "2022-05-28T18:00:00", + "2022-05-28T20:00:00", + "2022-05-28T22:00:00", + "2022-05-29T00:00:00", + "2022-05-29T02:00:00", + "2022-05-29T04:00:00", + "2022-05-29T06:00:00", + "2022-05-29T08:00:00", + "2022-05-29T10:00:00", + "2022-05-29T12:00:00", + "2022-05-29T14:00:00", + "2022-05-29T16:00:00", + "2022-05-29T18:00:00", + "2022-05-29T20:00:00", + "2022-05-29T22:00:00", + "2022-05-30T00:00:00", + "2022-05-30T02:00:00", + "2022-05-30T04:00:00", + "2022-05-30T06:00:00", + "2022-05-30T08:00:00", + "2022-05-30T10:00:00", + "2022-05-30T12:00:00", + "2022-05-30T14:00:00", + "2022-05-30T16:00:00", + "2022-05-30T18:00:00", + "2022-05-30T20:00:00", + "2022-05-30T22:00:00", + "2022-05-31T00:00:00", + "2022-05-31T02:00:00", + "2022-05-31T04:00:00", + "2022-05-31T06:00:00", + "2022-05-31T08:00:00", + "2022-05-31T10:00:00", + "2022-05-31T12:00:00", + "2022-05-31T14:00:00", + "2022-05-31T16:00:00", + "2022-05-31T18:00:00", + "2022-05-31T20:00:00", + "2022-05-31T22:00:00", + "2022-06-01T00:00:00", + "2022-06-01T02:00:00", + "2022-06-01T04:00:00", + "2022-06-01T06:00:00", + "2022-06-01T08:00:00", + "2022-06-01T10:00:00", + "2022-06-01T12:00:00", + "2022-06-01T14:00:00", + "2022-06-01T16:00:00", + "2022-06-01T18:00:00", + "2022-06-01T20:00:00", + "2022-06-01T22:00:00", + "2022-06-02T00:00:00", + "2022-06-02T02:00:00", + "2022-06-02T04:00:00", + "2022-06-02T06:00:00", + "2022-06-02T08:00:00", + "2022-06-02T10:00:00", + "2022-06-02T12:00:00", + "2022-06-02T14:00:00", + "2022-06-02T16:00:00", + "2022-06-02T18:00:00", + "2022-06-02T20:00:00", + "2022-06-02T22:00:00", + "2022-06-03T00:00:00", + "2022-06-03T02:00:00", + "2022-06-03T04:00:00", + "2022-06-03T06:00:00", + "2022-06-03T08:00:00", + "2022-06-03T10:00:00", + "2022-06-03T12:00:00", + "2022-06-03T14:00:00", + "2022-06-03T16:00:00", + "2022-06-03T18:00:00", + "2022-06-03T20:00:00", + "2022-06-03T22:00:00", + "2022-06-04T00:00:00", + "2022-06-04T02:00:00", + "2022-06-04T04:00:00", + "2022-06-04T06:00:00", + "2022-06-04T08:00:00", + "2022-06-04T10:00:00", + "2022-06-04T12:00:00", + "2022-06-04T14:00:00", + "2022-06-04T16:00:00", + "2022-06-04T18:00:00", + "2022-06-04T20:00:00", + "2022-06-04T22:00:00", + "2022-06-05T00:00:00", + "2022-06-05T02:00:00", + "2022-06-05T04:00:00", + "2022-06-05T06:00:00", + "2022-06-05T08:00:00", + "2022-06-05T10:00:00", + "2022-06-05T12:00:00", + "2022-06-05T14:00:00", + "2022-06-05T16:00:00", + "2022-06-05T18:00:00", + "2022-06-05T20:00:00", + "2022-06-05T22:00:00", + "2022-06-06T00:00:00", + "2022-06-06T02:00:00", + "2022-06-06T04:00:00", + "2022-06-06T06:00:00", + "2022-06-06T08:00:00", + "2022-06-06T10:00:00", + "2022-06-06T12:00:00", + "2022-06-06T14:00:00", + "2022-06-06T16:00:00", + "2022-06-06T18:00:00", + "2022-06-06T20:00:00", + "2022-06-06T22:00:00", + "2022-06-07T00:00:00", + "2022-06-07T02:00:00", + "2022-06-07T04:00:00", + "2022-06-07T06:00:00", + "2022-06-07T08:00:00", + "2022-06-07T10:00:00", + "2022-06-07T12:00:00", + "2022-06-07T14:00:00", + "2022-06-07T16:00:00", + "2022-06-07T18:00:00", + "2022-06-07T20:00:00", + "2022-06-07T22:00:00", + "2022-06-08T00:00:00", + "2022-06-08T02:00:00", + "2022-06-08T04:00:00", + "2022-06-08T06:00:00", + "2022-06-08T08:00:00", + "2022-06-08T10:00:00", + "2022-06-08T12:00:00", + "2022-06-08T14:00:00", + "2022-06-08T16:00:00", + "2022-06-08T18:00:00", + "2022-06-08T20:00:00", + "2022-06-08T22:00:00", + "2022-06-09T00:00:00", + "2022-06-09T02:00:00", + "2022-06-09T04:00:00", + "2022-06-09T06:00:00", + "2022-06-09T08:00:00", + "2022-06-09T10:00:00", + "2022-06-09T12:00:00", + "2022-06-09T14:00:00", + "2022-06-09T16:00:00", + "2022-06-09T18:00:00", + "2022-06-09T20:00:00", + "2022-06-09T22:00:00", + "2022-06-10T00:00:00", + "2022-06-10T02:00:00", + "2022-06-10T04:00:00", + "2022-06-10T06:00:00", + "2022-06-10T08:00:00", + "2022-06-10T10:00:00", + "2022-06-10T12:00:00", + "2022-06-10T14:00:00", + "2022-06-10T16:00:00", + "2022-06-10T18:00:00", + "2022-06-10T20:00:00", + "2022-06-10T22:00:00", + "2022-06-11T00:00:00", + "2022-06-11T02:00:00", + "2022-06-11T04:00:00", + "2022-06-11T06:00:00", + "2022-06-11T08:00:00", + "2022-06-11T10:00:00", + "2022-06-11T12:00:00", + "2022-06-11T14:00:00", + "2022-06-11T16:00:00", + "2022-06-11T18:00:00", + "2022-06-11T20:00:00", + "2022-06-11T22:00:00", + "2022-06-12T00:00:00", + "2022-06-12T02:00:00", + "2022-06-12T04:00:00", + "2022-06-12T06:00:00", + "2022-06-12T08:00:00", + "2022-06-12T10:00:00", + "2022-06-12T12:00:00", + "2022-06-12T14:00:00", + "2022-06-12T16:00:00", + "2022-06-12T18:00:00", + "2022-06-12T20:00:00", + "2022-06-12T22:00:00", + "2022-06-13T00:00:00", + "2022-06-13T02:00:00", + "2022-06-13T04:00:00", + "2022-06-13T06:00:00", + "2022-06-13T08:00:00", + "2022-06-13T10:00:00", + "2022-06-13T12:00:00", + "2022-06-13T14:00:00", + "2022-06-13T16:00:00", + "2022-06-13T18:00:00", + "2022-06-13T20:00:00", + "2022-06-13T22:00:00", + "2022-06-14T00:00:00", + "2022-06-14T02:00:00", + "2022-06-14T04:00:00", + "2022-06-14T06:00:00", + "2022-06-14T08:00:00", + "2022-06-14T10:00:00", + "2022-06-14T12:00:00", + "2022-06-14T14:00:00", + "2022-06-14T16:00:00", + "2022-06-14T18:00:00", + "2022-06-14T20:00:00", + "2022-06-14T22:00:00", + "2022-06-15T00:00:00", + "2022-06-15T02:00:00", + "2022-06-15T04:00:00", + "2022-06-15T06:00:00", + "2022-06-15T08:00:00", + "2022-06-15T10:00:00", + "2022-06-15T12:00:00", + "2022-06-15T14:00:00", + "2022-06-15T16:00:00", + "2022-06-15T18:00:00", + "2022-06-15T20:00:00", + "2022-06-15T22:00:00", + "2022-06-16T00:00:00", + "2022-06-16T02:00:00", + "2022-06-16T04:00:00", + "2022-06-16T06:00:00", + "2022-06-16T08:00:00", + "2022-06-16T10:00:00", + "2022-06-16T12:00:00", + "2022-06-16T14:00:00", + "2022-06-16T16:00:00", + "2022-06-16T18:00:00", + "2022-06-16T20:00:00", + "2022-06-16T22:00:00", + "2022-06-17T00:00:00", + "2022-06-17T02:00:00", + "2022-06-17T04:00:00", + "2022-06-17T06:00:00", + "2022-06-17T08:00:00", + "2022-06-17T10:00:00", + "2022-06-17T12:00:00", + "2022-06-17T14:00:00", + "2022-06-17T16:00:00", + "2022-06-17T18:00:00", + "2022-06-17T20:00:00", + "2022-06-17T22:00:00", + "2022-06-18T00:00:00", + "2022-06-18T02:00:00", + "2022-06-18T04:00:00", + "2022-06-18T06:00:00", + "2022-06-18T08:00:00", + "2022-06-18T10:00:00", + "2022-06-18T12:00:00", + "2022-06-18T14:00:00", + "2022-06-18T16:00:00", + "2022-06-18T18:00:00", + "2022-06-18T20:00:00", + "2022-06-18T22:00:00", + "2022-06-19T00:00:00", + "2022-06-19T02:00:00", + "2022-06-19T04:00:00", + "2022-06-19T06:00:00", + "2022-06-19T08:00:00", + "2022-06-19T10:00:00", + "2022-06-19T12:00:00", + "2022-06-19T14:00:00", + "2022-06-19T16:00:00", + "2022-06-19T18:00:00", + "2022-06-19T20:00:00", + "2022-06-19T22:00:00", + "2022-06-20T00:00:00" ], + "xaxis": "x", "y": [ - 42439.0, - 43107.0, - 43012.0, - 43059.0, - 43312.0, - 43373.0, - 42955.0, - 43201.0, - 43123.0, - 43483.0, - 43344.0, - 43259.0, - 43616.0, - 43455.0, - 43705.0, - 43398.0, - 43366.0, - 42972.0, - 43196.0, - 42987.0, - 43180.0, - 43341.0, - 42905.0, - 42770.0, - 42822.0, - 42687.0, - 42521.0, - 42773.0, - 42765.0, - 43061.0, - 43170.0, - 44209.0, - 45099.0, - 44902.0, - 44856.0, - 45334.0, - 45095.0, - 45071.0, - 45072.0, - 45257.0, - 45048.0, - 45129.0, - 45098.0, - 45237.0, - 45234.0, - 45012.0, - 44934.0, - 45099.0, - 44965.0, - 45009.0, - 45029.0, - 44971.0, - 44879.0, - 45025.0, - 44862.0, - 44886.0, - 45049.0, - 45080.0, - 44640.0, - 44749.0, - 44006.0, - 44170.0, - 44227.0, - 44252.0, - 44063.0, - 44087.0, - 44308.0, - 44497.0, - 44537.0, - 44880.0, - 44903.0, - 44935.0, - 45038.0, - 44999.0, - 44761.0, - 44940.0, - 44910.0, - 45403.0, - 45494.0, - 45830.0, - 45275.0, - 44985.0, - 44771.0, - 44078.0, - 44030.0, - 43501.0, - 43534.0, - 43306.0, - 43481.0, - 43300.0, - 43374.0, - 42502.0, - 42240.0, - 42098.0, - 42055.0, - 42254.0, - 42455.0, - 42377.0, - 42125.0, - 42516.0, - 42270.0, - 41889.0, - 42291.0, - 41938.0, - 41540.0, - 41650.0, - 40936.0, - 41079.0, - 41060.0, - 41316.0, - 41544.0, - 41380.0, - 42000.0, - 41850.0, - 41627.0, - 42164.0, - 42030.0, - 41775.0, - 41633.0, - 41666.0, - 41762.0, - 41802.0, - 41729.0, - 42660.0, - 42403.0, - 42279.0, - 42214.0, - 41347.0, - 40189.0, - 40776.0, - 40687.0, - 41214.0, - 41213.0, - 41549.0, - 41546.0, - 41466.0, - 41220.0, - 41456.0, - 41557.0, - 42062.0, - 41694.0, - 41651.0, - 42034.0, - 41735.0, - 41688.0, - 41476.0, - 41575.0, - 41600.0, - 41624.0, - 41528.0, - 41715.0, - 41741.0, - 41302.0, - 41412.0, - 41645.0, - 41515.0, - 41287.0, - 41582.0, - 41516.0, - 41409.0, - 41758.0, - 41562.0, - 41369.0, - 41305.0, - 41057.0, - 40776.0, - 41113.0, - 40757.0, - 40990.0, - 40824.0, - 41257.0, - 41034.0, - 41410.0, - 41604.0, - 41450.0, - 41233.0, - 40804.0, - 40971.0, - 40642.0, - 40888.0, - 41112.0, - 41009.0, - 41052.0, - 41501.0, - 40921.0, - 40999.0, - 41151.0, - 40601.0, - 39532.0, - 38601.0, - 38448.0, - 38520.0, - 37866.0, - 38077.0, - 38331.0, - 37700.0, - 37935.0, - 38164.0, - 38052.0, - 38123.0, - 38533.0, - 38208.0, - 38165.0, - 38086.0, - 37041.0, - 37042.0, - 36891.0, - 37493.0, - 37417.0, - 36724.0, - 36464.0, - 37003.0, - 36781.0, - 36899.0, - 36588.0, - 36844.0, - 36808.0, - 36969.0, - 36979.0, - 37218.0, - 37023.0, - 36999.0, - 36708.0, - 35971.0, - 35979.0, - 36829.0, - 36734.0, - 36859.0, - 36859.0, - 36772.0, - 36878.0, - 36600.0, - 36916.0, - 36593.0, - 36687.0, - 37144.0, - 37722.0, - 37284.0, - 36914.0, - 36830.0, - 37080.0, - 37138.0, - 37172.0, - 36980.0, - 36700.0, - 36129.0, - 36217.0, - 36350.0, - 36453.0, - 36868.0, - 36908.0, - 37011.0, - 37253.0, - 37122.0, - 37254.0, - 37074.0, - 36947.0, - 36855.0, - 36839.0, - 37539.0, - 37881.0, - 37579.0, - 37608.0, - 37514.0, - 37510.0, - 37598.0, - 37527.0, - 37670.0, - 37846.0, - 38580.0, - 38183.0, - 38401.0, - 38276.0, - 38327.0, - 38424.0, - 38098.0, - 38056.0, - 38240.0, - 38177.0, - 38299.0, - 38068.0, - 38313.0, - 37761.0, - 37390.0, - 37268.0, - 37428.0, - 37178.0, - 37167.0, - 37228.0, - 37417.0, - 37187.0, - 37180.0, - 36735.0, - 36899.0, - 37706.0, - 37649.0, - 37804.0, - 37937.0, - 37750.0, - 37682.0, - 37706.0, - 37792.0, - 37855.0, - 37858.0, - 37805.0, - 37695.0, - 38112.0, - 38092.0, - 38207.0, - 37968.0, - 37760.0, - 37782.0, - 37803.0, - 37743.0, - 37914.0, - 37827.0, - 37712.0, - 37889.0, - 37930.0, - 37746.0, - 37585.0, - 37669.0, - 37765.0, - 37618.0, - 37401.0, - 37403.0, - 37387.0, - 37533.0, - 37407.0, - 37409.0, - 37288.0, - 36958.0, - 37093.0, - 36617.0, - 37022.0, - 37041.0, - 36934.0, - 36991.0, - 36905.0, - 36814.0, - 36790.0, - 36633.0, - 36709.0, - 36631.0, - 36848.0, - 37447.0, - 37417.0, - 37526.0, - 36852.0, - 36881.0, - 36424.0, - 36713.0, - 37046.0, - 37240.0, - 37065.0, - 37172.0, - 36952.0, - 36813.0, - 36759.0, - 36793.0, - 36971.0, - 36955.0, - 37050.0, - 37088.0, - 37150.0, - 37342.0, - 38146.0, - 37993.0, - 37984.0, - 36523.0, - 36020.0, - 35168.0, - 34031.0, - 34414.0, - 34589.0, - 34389.0, - 34343.0, - 33891.0, - 34272.0, - 33861.0, - 33518.0, - 32405.0, - 32183.0, - 32248.0, - 32099.0, - 31533.0, - 31228.0, - 30676.0, - 31297.0, - 31469.0, - 30774.0, - 30579.0, - 30473.0, - 31225.0, - 30981.0, - 31171.0, - 30988.0, - 31343.0, - 31450.0, - 31721.0, - 31586.0, - 31850.0, - 31192.0, - 31244.0, - 30696.0, - 31163.0, - 32015.0, - 31485.0, - 31235.0, - 31088.0, - 30811.0, - 30496.0, - 29799.0, - 29543.0, - 30289.0, - 31238.0, - 31866.0, - 32555.0, - 32403.0, - 31918.0, - 32241.0, - 31904.0, - 31933.0, - 32276.0, - 32367.0, - 32417.0, - 32470.0, - 33059.0, - 32845.0, - 32382.0, - 32703.0, - 32607.0, - 33144.0, - 33324.0, - 33140.0, - 33399.0, - 33842.0, - 33914.0, - 33708.0, - 33751.0, - 33448.0, - 32343.0, - 32778.0, - 31920.0, - 31898.0, - 32132.0, - 32353.0, - 32676.0, - 32920.0, - 33047.0, - 32909.0, - 32616.0, - 32536.0, - 32468.0, - 33389.0, - 33123.0, - 33537.0, - 33388.0, - 33054.0, - 32805.0, - 32741.0, - 33089.0, - 33355.0, - 33054.0, - 33423.0, - 33887.0, - 33883.0, - 33694.0, - 33926.0, - 33856.0, - 34047.0, - 33927.0, - 33933.0, - 34271.0, - 33758.0, - 33840.0, - 34026.0, - 34476.0, - 34293.0, - 34106.0, - 34088.0, - 34281.0, - 34395.0, - 34077.0, - 34275.0, - 34131.0, - 34151.0, - 34146.0, - 33693.0, - 33903.0, - 34048.0, - 33030.0, - 33220.0, - 33192.0, - 33283.0, - 33320.0, - 33443.0, - 33349.0, - 33682.0, - 34347.0, - 34204.0, - 34237.0, - 34296.0, - 34101.0, - 34370.0, - 34262.0, - 34143.0, - 34009.0, - 34174.0, - 34507.0, - 34385.0, - 34612.0, - 34134.0, - 34473.0, - 34379.0, - 34130.0, - 34251.0, - 34123.0, - 33963.0, - 34158.0, - 34063.0, - 33920.0, - 33167.0, - 33162.0, - 33277.0, - 32716.0, - 32693.0, - 32690.0, - 32629.0, - 32798.0, - 32848.0, - 32478.0, - 32640.0, - 32238.0, - 32378.0, - 32218.0, - 32007.0, - 32339.0, - 32606.0, - 32506.0, - 32595.0, - 32795.0, - 33053.0, - 33203.0, - 32977.0, - 32776.0, - 34545.0, - 35412.0, - 35573.0, - 35484.0, - 36296.0, - 36112.0, - 36205.0, - 36315.0, - 36166.0, - 36322.0, - 36232.0, - 36316.0, - 36536.0, - 36365.0, - 36327.0, - 36452.0, - 36221.0, - 36363.0, - 36248.0, - 36334.0, - 36400.0, - 36445.0, - 36337.0, - 36470.0, - 36350.0, - 36397.0, - 36468.0, - 36371.0, - 37015.0, - 36835.0, - 37559.0, - 37483.0, - 37438.0, - 37195.0, - 37365.0, - 37473.0, - 38384.0, - 38752.0, - 38751.0, - 38540.0, - 38361.0, - 38582.0, - 38671.0, - 39384.0, - 39443.0, - 38474.0, - 38536.0, - 38265.0, - 38066.0, - 37659.0, - 37739.0, - 38776.0, - 38568.0, - 38454.0, - 37976.0, - 38228.0, - 38453.0, - 37997.0, - 38315.0, - 38476.0, - 38460.0, - 38586.0, - 38923.0, - 38965.0, - 38875.0, - 38799.0, - 38366.0, - 38481.0, - 38635.0, - 38868.0, - 39276.0, - 38317.0, - 39249.0, - 39593.0, - 38804.0, - 38327.0, - 38122.0, - 37870.0, - 38100.0, - 37692.0, - 38090.0, - 38250.0, - 38076.0, - 38489.0, - 38335.0, - 38156.0, - 37719.0, - 37506.0, - 37359.0, - 37442.0, - 37361.0, - 37352.0, - 37165.0, - 37451.0, - 37400.0, - 37134.0, - 37140.0, - 37672.0, - 37584.0, - 36950.0, - 37236.0, - 37276.0, - 37281.0, - 37324.0, - 37445.0, - 37374.0, - 37506.0, - 37521.0, - 37538.0, - 37296.0, - 37031.0, - 37257.0, - 37000.0, - 36744.0, - 36990.0, - 36786.0, - 37307.0, - 37176.0, - 37239.0, - 37612.0, - 37685.0, - 37649.0, - 37335.0, - 37354.0, - 37628.0, - 38409.0, - 38475.0, - 38498.0, - 38665.0, - 38875.0, - 39015.0, - 39012.0, - 38966.0, - 38691.0, - 38768.0, - 38755.0, - 39232.0, - 38755.0, - 38795.0, - 38847.0, - 38857.0, - 38775.0, - 38857.0, - 38435.0, - 38395.0, - 38322.0, - 38856.0, - 38759.0, - 38575.0, - 38733.0, - 38490.0, - 38434.0, - 38671.0, - 38065.0, - 38024.0, - 37199.0, - 36922.0, - 36761.0, - 36294.0, - 35849.0, - 35729.0, - 35924.0, - 35846.0, - 35795.0, - 35766.0, - 36014.0, - 35367.0, - 35632.0, - 35137.0, - 35398.0, - 35480.0, - 35335.0, - 35353.0, - 35487.0, - 35495.0, - 35594.0, - 35607.0, - 35312.0, - 35213.0, - 35374.0, - 35343.0, - 35474.0, - 35299.0, - 35254.0, - 35387.0, - 35346.0, - 35171.0, - 34743.0, - 34218.0, - 33690.0, - 33803.0, - 33726.0, - 33870.0, - 33779.0, - 33920.0, - 33798.0, - 33911.0, - 34480.0, - 34311.0, - 34568.0, - 34600.0, - 34296.0, - 33131.0, - 33147.0, - 34286.0, - 34022.0, - 33683.0, - 32759.0, - 32723.0, - 32932.0, - 32347.0, - 32532.0, - 32551.0, - 32823.0, - 33101.0, - 33401.0, - 33337.0, - 33205.0, - 33604.0, - 33475.0, - 33748.0, - 33597.0, - 33332.0, - 33586.0, - 33722.0, - 34272.0, - 34205.0, - 34352.0, - 34002.0, - 33685.0, - 33289.0, - 33200.0, - 32921.0, - 32754.0, - 31133.0, - 30829.0, - 31402.0, - 31499.0, - 31730.0, - 31824.0, - 32355.0, - 32102.0, - 33380.0, - 34317.0, - 34264.0, - 34457.0, - 34747.0, - 34281.0, - 34350.0, - 34628.0, - 34677.0, - 35083.0, - 35065.0, - 34724.0, - 34397.0, - 34621.0, - 34801.0, - 35275.0, - 35037.0, - 34668.0, - 34848.0, - 34494.0, - 34629.0, - 34758.0, - 34866.0, - 34896.0, - 34875.0, - 35032.0, - 34691.0, - 33964.0, - 34313.0, - 34304.0, - 34492.0, - 34708.0, - 35029.0, - 34671.0, - 34912.0, - 34562.0, - 33474.0, - 33572.0, - 33733.0, - 34046.0, - 33895.0, - 33957.0, - 34294.0, - 34147.0, - 34274.0, - 33961.0, - 36381.0, - 36946.0, - 36874.0, - 37104.0, - 38534.0, - 38598.0, - 38594.0, - 38612.0, - 38745.0, - 38670.0, - 38999.0, - 39871.0, - 39174.0, - 39332.0, - 39315.0, - 39466.0, - 39942.0, - 39609.0, - 39871.0, - 39883.0, - 39660.0, - 39618.0, - 39785.0, - 39203.0, - 39897.0, - 39470.0, - 39324.0, - 39646.0, - 39505.0, - 39508.0, - 39073.0, - 39075.0, - 39197.0, - 38918.0, - 39146.0, - 39717.0, - 38490.0, - 38344.0, - 38180.0, - 38027.0, - 38371.0, - 37611.0, - 37607.0, - 37546.0, - 37625.0, - 37708.0, - 37958.0, - 37837.0, - 37233.0, - 37389.0, - 36394.0, - 36041.0, - 35832.0, - 35681.0, - 35758.0, - 35704.0, - 35772.0, - 35778.0, - 35850.0, - 35719.0, - 35838.0, - 36144.0, - 36145.0, - 36064.0, - 36085.0, - 36113.0, - 36049.0, - 36183.0, - 36112.0, - 35446.0, - 35147.0, - 35594.0, - 35542.0, - 35720.0, - 35417.0, - 35743.0, - 35331.0, - 35318.0, - 34863.0, - 34993.0, - 34869.0, - 35160.0, - 35357.0, - 35842.0, - 35913.0, - 35479.0, - 34388.0, - 34901.0, - 34983.0, - 35266.0, - 35575.0, - 35321.0, - 35486.0, - 35801.0, - 35808.0, - 35589.0, - 35532.0, - 35745.0, - 35584.0, - 35351.0, - 35544.0, - 35864.0, - 37785.0, - 37921.0, - 38280.0, - 38346.0, - 38407.0, - 37972.0, - 38478.0, - 38145.0, - 38160.0, - 37796.0, - 37858.0, - 37129.0, - 36912.0, - 35491.0, - 35500.0, - 35505.0, - 35446.0, - 35182.0, - 35606.0, - 35640.0, - 35899.0, - 35798.0, - 35828.0, - 35165.0, - 35214.0, - 35300.0, - 35617.0, - 35604.0, - 36226.0, - 35747.0, - 35669.0, - 35587.0, - 35582.0, - 35606.0, - 35507.0, - 35935.0, - 35855.0, - 35900.0, - 35840.0, - 35897.0, - 35838.0, - 35818.0, - 35850.0, - 35834.0, - 35820.0, - 35875.0, - 35605.0, - 35760.0, - 35946.0, - 35929.0, - 35790.0, - 35773.0, - 35556.0, - 35526.0, - 35676.0, - 35690.0, - 35600.0, - 35425.0, - 34601.0, - 34917.0, - 35031.0, - 35286.0, - 35747.0, - 35664.0, - 35569.0, - 35557.0, - 35505.0, - 35321.0, - 35468.0, - 35418.0, - 36256.0, - 35440.0, - 35360.0, - 35307.0, - 34951.0, - 35036.0, - 35215.0, - 35214.0, - 35548.0, - 35914.0, - 36249.0, - 36193.0, - 35848.0, - 36030.0, - 35733.0, - 35838.0, - 36090.0, - 36872.0, - 36795.0, - 36914.0, - 36975.0, - 36781.0, - 37000.0, - 37267.0, - 37282.0, - 37325.0, - 37148.0, - 37274.0, - 36904.0, - 36931.0, - 36882.0, - 37193.0, - 36771.0, - 36691.0, - 36805.0, - 36983.0, - 36910.0, - 36580.0, - 36622.0, - 36786.0, - 36642.0, - 36635.0, - 36674.0, - 36848.0, - 37077.0, - 37717.0, - 38057.0, - 37834.0, - 37775.0, - 37729.0, - 37838.0, - 37736.0, - 37618.0, - 37687.0, - 37733.0, - 37800.0, - 37973.0, - 37938.0, - 38060.0, - 38134.0, - 38151.0, - 37975.0, - 37920.0, - 37883.0, - 37894.0, - 37803.0, - 37644.0, - 37496.0, - 37386.0, - 37088.0, - 37452.0, - 37424.0, - 37351.0, - 37309.0, - 37005.0, - 37115.0, - 37186.0, - 37417.0, - 37420.0, - 37301.0, - 37338.0, - 37251.0, - 37358.0, - 37468.0, - 37240.0, - 37416.0, - 38340.0, - 39011.0, - 38472.0, - 38599.0, - 39079.0, - 38793.0, - 38646.0, - 38656.0, - 38349.0, - 38867.0, - 38401.0, - 38366.0, - 38206.0, - 38136.0, - 38221.0, - 38331.0, - 38294.0, - 38313.0, - 38737.0, - 38337.0, - 38400.0, - 38497.0, - 38953.0, - 38956.0, - 38892.0, - 39172.0, - 39266.0, - 39140.0, - 39139.0, - 39184.0, - 39948.0, - 39871.0, - 39943.0, - 40016.0, - 39982.0, - 39977.0, - 39898.0, - 39994.0, - 39863.0, - 40081.0, - 40471.0, - 40854.0, - 40294.0, - 40301.0, - 40488.0, - 40550.0, - 40387.0, - 40336.0, - 40489.0, - 40594.0, - 40603.0, - 40577.0, - 40451.0, - 40341.0, - 40294.0, - 40418.0, - 40434.0, - 40504.0, - 40585.0, - 40771.0, - 40699.0, - 40682.0, - 40784.0, - 40613.0, - 40590.0, - 40671.0, - 40840.0, - 40900.0, - 40810.0, - 42347.0, - 42669.0, - 42719.0, - 42902.0, - 42992.0, - 42907.0, - 42931.0, - 43132.0, - 43547.0, - 43179.0, - 43360.0, - 43659.0, - 43348.0, - 42838.0, - 43285.0, - 43078.0, - 43423.0, - 43048.0, - 43138.0, - 43226.0, - 42847.0, - 42690.0, - 42860.0, - 43061.0, - 42631.0, - 42768.0, - 42201.0, - 42711.0, - 42776.0, - 42558.0, - 42385.0, - 42465.0, - 42089.0, - 42248.0, - 42780.0, - 42244.0, - 42127.0, - 42195.0, - 42402.0, - 42169.0, - 42166.0, - 42333.0, - 42565.0, - 42499.0, - 42308.0, - 41857.0, - 41359.0, - 41245.0, - 41332.0, - 41130.0, - 40600.0, - 40318.0, - 40363.0, - 40750.0, - 40834.0, - 40787.0, - 41174.0, - 42130.0, - 42103.0, - 42000.0, - 41949.0, - 41920.0, - 42294.0, - 42174.0, - 42159.0, - 42217.0, - 42295.0, - 42192.0, - 42329.0, - 41893.0, - 41997.0, - 41769.0, - 41906.0, - 41474.0, - 41557.0, - 41655.0, - 41995.0, - 42034.0, - 42027.0, - 41889.0, - 42076.0, - 42032.0, - 42013.0, - 42001.0, - 42503.0, - 42060.0, - 41555.0, - 41641.0, - 41867.0, - 41842.0, - 41703.0, - 41965.0, - 42047.0, - 41524.0, - 41412.0, - 41900.0, - 42352.0, - 42465.0, - 42530.0, - 42497.0, - 42544.0, - 42514.0, - 42413.0, - 42566.0, - 42333.0, - 41930.0, - 42137.0, - 42209.0, - 42169.0, - 41742.0, - 41547.0, - 41521.0, - 41731.0, - 41578.0, - 41560.0, - 41072.0, - 40452.0, - 40504.0, - 40226.0, - 40175.0, - 40273.0, - 39609.0, - 39762.0, - 39708.0, - 39742.0, - 39874.0, - 39905.0, - 40126.0, - 40019.0, - 39789.0, - 40118.0, - 39937.0, - 40029.0, - 39980.0, - 40172.0, - 40127.0, - 40190.0, - 40281.0, - 40138.0, - 39865.0, - 39622.0, - 40142.0, - 39590.0, - 39285.0, - 39175.0, - 38893.0, - 38969.0, - 39002.0, - 39163.0, - 39102.0, - 39082.0, - 39072.0, - 39087.0, - 38831.0, - 39156.0, - 39140.0, - 39165.0, - 39370.0, - 39383.0, - 39394.0, - 39290.0, - 39232.0, - 39099.0, - 39306.0, - 39219.0, - 39370.0, - 39708.0, - 39705.0, - 39205.0, - 38740.0, - 38764.0, - 38726.0, - 38855.0, - 38817.0, - 38000.0, - 37684.0, - 37829.0, - 37333.0, - 37182.0, - 36729.0, - 36235.0, - 36318.0, - 36327.0, - 36570.0, - 36954.0, - 36989.0, - 36998.0, - 37200.0, - 37197.0, - 37046.0, - 36976.0, - 36294.0, - 36693.0, - 37042.0, - 36787.0, - 37012.0, - 37110.0, - 37006.0, - 37027.0, - 36729.0, - 37497.0, - 37934.0, - 37761.0, - 37760.0, - 37883.0, - 37784.0, - 38064.0, - 37912.0, - 37892.0, - 37824.0, - 37733.0, - 37622.0, - 37732.0, - 37024.0, - 36940.0, - 36723.0, - 36934.0, - 36941.0, - 36960.0, - 37220.0, - 37150.0, - 37271.0, - 37087.0, - 37173.0, - 37073.0, - 37396.0, - 37445.0, - 37349.0, - 37427.0, - 37551.0, - 37538.0, - 37406.0, - 37456.0, - 37422.0, - 37355.0, - 37393.0, - 37410.0, - 37109.0, - 37194.0, - 37267.0, - 37472.0, - 37365.0, - 37418.0, - 37288.0, - 37414.0, - 37303.0, - 37424.0, - 37490.0, - 37360.0, - 37464.0, - 37135.0, - 37356.0, - 37229.0, - 36724.0, - 36817.0, - 36905.0, - 36108.0, - 36088.0, - 36151.0, - 36114.0, - 36435.0, - 36436.0, - 37272.0, - 37712.0, - 37869.0, - 37851.0, - 37836.0, - 37801.0, - 37771.0, - 37661.0, - 37643.0, - 37722.0, - 38400.0, - 38424.0, - 38342.0, - 38404.0, - 38287.0, - 38434.0, - 38314.0, - 38307.0, - 38266.0, - 38299.0, - 38878.0, - 38351.0, - 38037.0, - 38146.0, - 37944.0, - 38270.0, - 38148.0, - 38471.0, - 38533.0, - 38251.0, - 38362.0, - 38336.0, - 39025.0, - 39265.0, - 38858.0, - 38293.0, - 38075.0, - 37615.0, - 37382.0, - 37443.0, - 37375.0, - 37532.0, - 37701.0, - 37325.0, - 37496.0, - 37244.0, - 36532.0, - 36493.0, - 36620.0, - 36730.0, - 36815.0, - 36722.0, - 36590.0, - 36702.0, - 36729.0, - 36671.0, - 36732.0, - 36838.0, - 36923.0, - 36907.0, - 36897.0, - 37019.0, - 36552.0, - 36599.0, - 36900.0, - 36767.0, - 36875.0, - 36835.0, - 36739.0, - 36691.0, - 36749.0, - 36564.0, - 36740.0, - 36700.0, - 36538.0, - 36322.0, - 36423.0, - 36182.0, - 35831.0, - 35981.0, - 36153.0, - 36161.0, - 36401.0, - 36783.0, - 37543.0, - 37529.0, - 37709.0, - 37754.0, - 37755.0, - 37839.0, - 37981.0, - 37806.0, - 37894.0, - 37252.0, - 36186.0, - 36082.0, - 35958.0, - 36057.0, - 35838.0, - 36013.0, - 36016.0, - 36053.0, - 36748.0, - 36798.0, - 36827.0, - 37313.0, - 36991.0, - 37007.0, - 36782.0, - 37189.0, - 37190.0, - 37192.0, - 37441.0, - 37420.0, - 37305.0, - 37832.0, - 37810.0, - 37176.0, - 37631.0, - 38342.0, - 38007.0, - 37945.0, - 37864.0, - 37867.0, - 37590.0, - 37487.0, - 37509.0, - 37018.0, - 36731.0, - 37342.0, - 36982.0, - 36592.0, - 36349.0, - 36602.0, - 36631.0, - 36733.0, - 36696.0, - 36733.0, - 36678.0, - 36678.0, - 36594.0, - 36559.0, - 36532.0, - 36361.0, - 36533.0, - 36385.0, - 35749.0, - 35999.0, - 36121.0, - 36151.0, - 36026.0, - 36076.0, - 36030.0, - 35968.0, - 36539.0, - 36433.0, - 35884.0, - 36376.0, - 36487.0, - 36576.0, - 36750.0, - 37100.0, - 36885.0, - 36845.0, - 36616.0, - 36966.0, - 36758.0, - 36501.0, - 36695.0, - 36781.0, - 36659.0, - 36566.0, - 36605.0, - 36665.0, - 36652.0, - 36581.0, - 36610.0, - 36344.0, - 36385.0, - 36183.0, - 35854.0, - 35902.0, - 35854.0, - 35991.0, - 36152.0, - 36214.0, - 36768.0, - 36936.0, - 37016.0, - 36694.0, - 36973.0, - 36979.0, - 37574.0, - 37376.0, - 37337.0, - 37274.0, - 37424.0, - 37300.0, - 37315.0, - 37209.0, - 37435.0, - 36949.0, - 35174.0, - 35085.0, - 34462.0, - 34598.0, - 34699.0, - 34495.0, - 34620.0, - 34701.0, - 34598.0, - 34452.0, - 33876.0, - 33799.0, - 34014.0, - 34027.0, - 34150.0, - 34113.0, - 34123.0, - 34122.0, - 33963.0, - 34038.0, - 34075.0, - 34106.0, - 34215.0, - 34080.0, - 34132.0, - 34059.0, - 34047.0, - 33200.0, - 33634.0, - 33010.0, - 32740.0, - 32700.0, - 32842.0, - 32798.0, - 32975.0, - 32600.0, - 32600.0, - 32316.0, - 32816.0, - 32686.0, - 32284.0, - 32323.0, - 31940.0, - 31860.0, - 31892.0, - 31767.0, - 31403.0, - 31303.0, - 30626.0, - 29633.0, - 29362.0, - 29220.0, - 28501.0, - 29243.0, - 29001.0, - 30110.0, - 29969.0, - 29815.0, - 29982.0, - 30169.0, - 29739.0, - 30073.0, - 29750.0, - 28750.0, - 29426.0, - 29220.0, - 29624.0, - 29829.0, - 28979.0, - 30016.0, - 29840.0, - 29377.0, - 29316.0, - 28385.0, - 27885.0, - 27557.0, - 27553.0, - 27672.0, - 26893.0, - 25502.0, - 26534.0, - 26399.0, - 27151.0, - 26913.0, - 27814.0, - 27533.0, - 27517.0, - 27325.0, - 27893.0, - 28339.0, - 29294.0, - 29337.0, - 28908.0, - 29338.0, - 29503.0, - 29643.0, - 29142.0, - 28478.0, - 28919.0, - 28837.0, - 28128.0, - 28520.0, - 28400.0, - 28274.0, - 28244.0, - 28314.0, - 27907.0, - 27673.0, - 27690.0, - 28458.0, - 28169.0, - 28513.0, - 28918.0, - 28766.0, - 28574.0, - 28591.0, - 28671.0, - 28615.0, - 29064.0, - 29008.0, - 28831.0, - 28795.0, - 29277.0, - 29901.0, - 30112.0, - 29551.0, - 29161.0, - 29074.0, - 28442.0, - 28421.0, - 28772.0, - 28348.0, - 28458.0, - 28480.0, - 28332.0, - 28899.0, - 28585.0, - 28654.0, - 29040.0, - 29173.0, - 29042.0, - 29217.0, - 28762.0, - 28961.0, - 28610.0, - 28505.0, - 28530.0, - 28942.0, - 28854.0, - 28570.0, - 28317.0, - 28467.0, - 28418.0, - 28446.0, - 28332.0, - 27851.0, - 27588.0, - 27625.0, - 27968.0, - 27853.0, - 27393.0, - 27414.0, - 27785.0, - 27662.0, - 27800.0, - 27603.0, - 28030.0, - 27873.0, - 28680.0, - 28419.0, - 28279.0, - 28558.0, - 28636.0, - 28789.0, - 28532.0, - 28450.0, - 28584.0, - 28566.0, - 28764.0, - 28733.0, - 27579.0, - 27440.0, - 27720.0, - 27619.0, - 27618.0, - 27582.0, - 27675.0, - 27717.0, - 27859.0, - 27747.0, - 27715.0, - 27737.0, - 27963.0, - 27821.0, - 27884.0, - 27750.0, - 27837.0, - 27888.0, - 27765.0, - 27782.0, - 27853.0, - 28479.0, - 28240.0, - 28418.0, - 28215.0, - 28282.0, - 28307.0, - 28393.0, - 28633.0, - 28493.0, - 28460.0, - 28538.0, - 28701.0, - 28486.0, - 28495.0, - 28405.0, - 28425.0, - 28143.0, - 27205.0, - 27446.0, - 27231.0, - 27381.0, - 27449.0, - 27482.0, - 27400.0, - 27375.0, - 27329.0, - 26923.0, - 27374.0, - 27335.0, - 27334.0, - 27540.0, - 27585.0, - 27778.0, - 28128.0, - 27840.0, - 27751.0, - 27877.0, - 27570.0, - 27840.0, - 27753.0, - 27831.0, - 27673.0, - 27905.0, - 27594.0, - 27746.0, - 27852.0, - 27825.0, - 27791.0, - 27252.0, - 27076.0, - 26887.0, - 27398.0, - 27650.0, - 27392.0, - 27542.0, - 27219.0, - 26846.0, - 27000.0, - 26776.0, - 26972.0, - 27082.0, - 26957.0, - 27280.0, - 26949.0, - 26443.0, - 26886.0, - 26729.0, - 26616.0, - 26625.0, - 26847.0, - 26894.0, - 26892.0, - 26842.0, - 26858.0, - 27081.0, - 27009.0, - 26889.0, - 27030.0, - 27027.0, - 27036.0, - 26917.0, - 27006.0, - 27051.0, - 27024.0, - 27015.0, - 27221.0, - 27309.0, - 27218.0, - 27225.0, - 27110.0, - 27221.0, - 27433.0, - 27633.0, - 28256.0, - 28289.0, - 28489.0, - 28509.0, - 28438.0, - 28314.0, - 28388.0, - 28470.0, - 28435.0, - 29500.0, - 29411.0, - 29336.0, - 29540.0, - 29440.0, - 29386.0, - 29377.0, - 29628.0, - 29297.0, - 29871.0, - 29927.0, - 29476.0, - 29527.0, - 29588.0, - 29781.0, - 29503.0, - 29528.0, - 29350.0, - 29543.0, - 29487.0, - 29641.0, - 28875.0, - 28306.0, - 28273.0, - 27776.0, - 27968.0, - 27986.0, - 27902.0, - 28011.0, - 28045.0, - 28006.0, - 28229.0, - 27937.0, - 28146.0, - 28092.0, - 28157.0, - 28235.0, - 28332.0, - 28444.0, - 28374.0, - 28395.0, - 28288.0, - 28138.0, - 27718.0, - 27643.0, - 27506.0, - 27523.0, - 27579.0, - 27807.0, - 27740.0, - 27591.0, - 27565.0, - 27709.0, - 27704.0, - 27720.0, - 27701.0, - 27635.0, - 27850.0, - 27750.0, - 27674.0, - 27860.0, - 27869.0, - 27709.0, - 27763.0, - 27798.0, - 27651.0, - 27662.0, - 27741.0, - 27672.0, - 27951.0, - 28023.0, - 28041.0, - 27944.0, - 27875.0, - 28742.0, - 29138.0, - 29004.0, - 29071.0, - 29247.0, - 29214.0, - 29308.0, - 29436.0, - 29237.0, - 29406.0, - 29440.0, - 29334.0, - 27718.0, - 27718.0, - 27647.0, - 27668.0, - 27678.0, - 27663.0, - 27660.0, - 27956.0, - 27990.0, - 28963.0, - 29268.0, - 29049.0, - 29080.0, - 28076.0, - 28585.0, - 28585.0, - 28497.0, - 28331.0, - 28632.0, - 28356.0, - 28312.0, - 28138.0, - 28347.0, - 28178.0, - 28105.0, - 28227.0, - 28272.0, - 28437.0, - 28476.0, - 28221.0, - 28223.0, - 28447.0, - 28465.0, - 28246.0, - 28367.0, - 28336.0, - 28125.0, - 28304.0, - 28309.0, - 28268.0, - 28190.0, - 28370.0, - 27909.0, - 27989.0, - 27664.0, - 27527.0, - 27745.0, - 27666.0, - 27751.0, - 27816.0, - 27854.0, - 27895.0, - 27642.0, - 27334.0, - 27381.0, - 27026.0, - 27114.0, - 27209.0, - 27141.0, - 27012.0, - 26810.0, - 26235.0, - 25957.0, - 26317.0, - 26155.0, - 26117.0, - 25984.0, - 26725.0, - 26750.0, - 26040.0, - 26007.0, - 25305.0, - 24576.0, - 24286.0, - 24034.0, - 23681.0, - 22947.0, - 22676.0, - 22646.0, - 22310.0, - 22526.0, - 22270.0, - 22162.0, - 21584.0, - 20091.0, - 21217.0, - 21811.0, - 21855.0, - 21361.0, - 21037.0, - 21473.0, - 21784.0, - 21455.0, - 21325.0, - 20624.0, - 21232.0, - 21211.0, - 20512.0, - 20264.0, - 19795.0, - 19254.0, - 20227.0, - 20638.0, - 20581.0, - 20275.0, - 20686.0, - 21241.0, - 21582.0, - 21426.0, - 21230.0, - 21033.0, - 20938.0, - 20410.0, - 20261.0, - 20111.0, - 19988.0, - 19890.0, - 19765.0, - 19552.0, - 19331.0, - 19796.0, - 19339.0, - 19760.0, - 20052.0, - 19910.0, - 19987.0, - 19811.0, - 19733.0, - 19589.0, - 19525.0, - 19566.0, - 19466.0, - 19442.0, - 19480.0, - 19445.0, - 18364.0, - 18275.0, - 18336.0, - 18292.0, - 18039.0, - 17710.0, - 17188.0, - 17614.0, - 18068.0, - 17558.0, - 17419.0, - 17264.0, - 17431.0, - 18098.0, - 18714.0, - 18280.0, - 18856.0, - 18523.0, - 19162.0, - 19450.0, - 19608.0, - 18851.0 + 42439, + 43107, + 43012, + 43059, + 43312, + 43373, + 42955, + 43201, + 43123, + 43483, + 43344, + 43259, + 43616, + 43455, + 43705, + 43398, + 43366, + 42972, + 43196, + 42987, + 43180, + 43341, + 42905, + 42770, + 42822, + 42687, + 42521, + 42773, + 42765, + 43061, + 43170, + 44209, + 45099, + 44902, + 44856, + 45334, + 45095, + 45071, + 45072, + 45257, + 45048, + 45129, + 45098, + 45237, + 45234, + 45012, + 44934, + 45099, + 44965, + 45009, + 45029, + 44971, + 44879, + 45025, + 44862, + 44886, + 45049, + 45080, + 44640, + 44749, + 44006, + 44170, + 44227, + 44252, + 44063, + 44087, + 44308, + 44497, + 44537, + 44880, + 44903, + 44935, + 45038, + 44999, + 44761, + 44940, + 44910, + 45403, + 45494, + 45830, + 45275, + 44985, + 44771, + 44078, + 44030, + 43501, + 43534, + 43306, + 43481, + 43300, + 43374, + 42502, + 42240, + 42098, + 42055, + 42254, + 42455, + 42377, + 42125, + 42516, + 42270, + 41889, + 42291, + 41938, + 41540, + 41650, + 40936, + 41079, + 41060, + 41316, + 41544, + 41380, + 42000, + 41850, + 41627, + 42164, + 42030, + 41775, + 41633, + 41666, + 41762, + 41802, + 41729, + 42660, + 42403, + 42279, + 42214, + 41347, + 40189, + 40776, + 40687, + 41214, + 41213, + 41549, + 41546, + 41466, + 41220, + 41456, + 41557, + 42062, + 41694, + 41651, + 42034, + 41735, + 41688, + 41476, + 41575, + 41600, + 41624, + 41528, + 41715, + 41741, + 41302, + 41412, + 41645, + 41515, + 41287, + 41582, + 41516, + 41409, + 41758, + 41562, + 41369, + 41305, + 41057, + 40776, + 41113, + 40757, + 40990, + 40824, + 41257, + 41034, + 41410, + 41604, + 41450, + 41233, + 40804, + 40971, + 40642, + 40888, + 41112, + 41009, + 41052, + 41501, + 40921, + 40999, + 41151, + 40601, + 39532, + 38601, + 38448, + 38520, + 37866, + 38077, + 38331, + 37700, + 37935, + 38164, + 38052, + 38123, + 38533, + 38208, + 38165, + 38086, + 37041, + 37042, + 36891, + 37493, + 37417, + 36724, + 36464, + 37003, + 36781, + 36899, + 36588, + 36844, + 36808, + 36969, + 36979, + 37218, + 37023, + 36999, + 36708, + 35971, + 35979, + 36829, + 36734, + 36859, + 36859, + 36772, + 36878, + 36600, + 36916, + 36593, + 36687, + 37144, + 37722, + 37284, + 36914, + 36830, + 37080, + 37138, + 37172, + 36980, + 36700, + 36129, + 36217, + 36350, + 36453, + 36868, + 36908, + 37011, + 37253, + 37122, + 37254, + 37074, + 36947, + 36855, + 36839, + 37539, + 37881, + 37579, + 37608, + 37514, + 37510, + 37598, + 37527, + 37670, + 37846, + 38580, + 38183, + 38401, + 38276, + 38327, + 38424, + 38098, + 38056, + 38240, + 38177, + 38299, + 38068, + 38313, + 37761, + 37390, + 37268, + 37428, + 37178, + 37167, + 37228, + 37417, + 37187, + 37180, + 36735, + 36899, + 37706, + 37649, + 37804, + 37937, + 37750, + 37682, + 37706, + 37792, + 37855, + 37858, + 37805, + 37695, + 38112, + 38092, + 38207, + 37968, + 37760, + 37782, + 37803, + 37743, + 37914, + 37827, + 37712, + 37889, + 37930, + 37746, + 37585, + 37669, + 37765, + 37618, + 37401, + 37403, + 37387, + 37533, + 37407, + 37409, + 37288, + 36958, + 37093, + 36617, + 37022, + 37041, + 36934, + 36991, + 36905, + 36814, + 36790, + 36633, + 36709, + 36631, + 36848, + 37447, + 37417, + 37526, + 36852, + 36881, + 36424, + 36713, + 37046, + 37240, + 37065, + 37172, + 36952, + 36813, + 36759, + 36793, + 36971, + 36955, + 37050, + 37088, + 37150, + 37342, + 38146, + 37993, + 37984, + 36523, + 36020, + 35168, + 34031, + 34414, + 34589, + 34389, + 34343, + 33891, + 34272, + 33861, + 33518, + 32405, + 32183, + 32248, + 32099, + 31533, + 31228, + 30676, + 31297, + 31469, + 30774, + 30579, + 30473, + 31225, + 30981, + 31171, + 30988, + 31343, + 31450, + 31721, + 31586, + 31850, + 31192, + 31244, + 30696, + 31163, + 32015, + 31485, + 31235, + 31088, + 30811, + 30496, + 29799, + 29543, + 30289, + 31238, + 31866, + 32555, + 32403, + 31918, + 32241, + 31904, + 31933, + 32276, + 32367, + 32417, + 32470, + 33059, + 32845, + 32382, + 32703, + 32607, + 33144, + 33324, + 33140, + 33399, + 33842, + 33914, + 33708, + 33751, + 33448, + 32343, + 32778, + 31920, + 31898, + 32132, + 32353, + 32676, + 32920, + 33047, + 32909, + 32616, + 32536, + 32468, + 33389, + 33123, + 33537, + 33388, + 33054, + 32805, + 32741, + 33089, + 33355, + 33054, + 33423, + 33887, + 33883, + 33694, + 33926, + 33856, + 34047, + 33927, + 33933, + 34271, + 33758, + 33840, + 34026, + 34476, + 34293, + 34106, + 34088, + 34281, + 34395, + 34077, + 34275, + 34131, + 34151, + 34146, + 33693, + 33903, + 34048, + 33030, + 33220, + 33192, + 33283, + 33320, + 33443, + 33349, + 33682, + 34347, + 34204, + 34237, + 34296, + 34101, + 34370, + 34262, + 34143, + 34009, + 34174, + 34507, + 34385, + 34612, + 34134, + 34473, + 34379, + 34130, + 34251, + 34123, + 33963, + 34158, + 34063, + 33920, + 33167, + 33162, + 33277, + 32716, + 32693, + 32690, + 32629, + 32798, + 32848, + 32478, + 32640, + 32238, + 32378, + 32218, + 32007, + 32339, + 32606, + 32506, + 32595, + 32795, + 33053, + 33203, + 32977, + 32776, + 34545, + 35412, + 35573, + 35484, + 36296, + 36112, + 36205, + 36315, + 36166, + 36322, + 36232, + 36316, + 36536, + 36365, + 36327, + 36452, + 36221, + 36363, + 36248, + 36334, + 36400, + 36445, + 36337, + 36470, + 36350, + 36397, + 36468, + 36371, + 37015, + 36835, + 37559, + 37483, + 37438, + 37195, + 37365, + 37473, + 38384, + 38752, + 38751, + 38540, + 38361, + 38582, + 38671, + 39384, + 39443, + 38474, + 38536, + 38265, + 38066, + 37659, + 37739, + 38776, + 38568, + 38454, + 37976, + 38228, + 38453, + 37997, + 38315, + 38476, + 38460, + 38586, + 38923, + 38965, + 38875, + 38799, + 38366, + 38481, + 38635, + 38868, + 39276, + 38317, + 39249, + 39593, + 38804, + 38327, + 38122, + 37870, + 38100, + 37692, + 38090, + 38250, + 38076, + 38489, + 38335, + 38156, + 37719, + 37506, + 37359, + 37442, + 37361, + 37352, + 37165, + 37451, + 37400, + 37134, + 37140, + 37672, + 37584, + 36950, + 37236, + 37276, + 37281, + 37324, + 37445, + 37374, + 37506, + 37521, + 37538, + 37296, + 37031, + 37257, + 37000, + 36744, + 36990, + 36786, + 37307, + 37176, + 37239, + 37612, + 37685, + 37649, + 37335, + 37354, + 37628, + 38409, + 38475, + 38498, + 38665, + 38875, + 39015, + 39012, + 38966, + 38691, + 38768, + 38755, + 39232, + 38755, + 38795, + 38847, + 38857, + 38775, + 38857, + 38435, + 38395, + 38322, + 38856, + 38759, + 38575, + 38733, + 38490, + 38434, + 38671, + 38065, + 38024, + 37199, + 36922, + 36761, + 36294, + 35849, + 35729, + 35924, + 35846, + 35795, + 35766, + 36014, + 35367, + 35632, + 35137, + 35398, + 35480, + 35335, + 35353, + 35487, + 35495, + 35594, + 35607, + 35312, + 35213, + 35374, + 35343, + 35474, + 35299, + 35254, + 35387, + 35346, + 35171, + 34743, + 34218, + 33690, + 33803, + 33726, + 33870, + 33779, + 33920, + 33798, + 33911, + 34480, + 34311, + 34568, + 34600, + 34296, + 33131, + 33147, + 34286, + 34022, + 33683, + 32759, + 32723, + 32932, + 32347, + 32532, + 32551, + 32823, + 33101, + 33401, + 33337, + 33205, + 33604, + 33475, + 33748, + 33597, + 33332, + 33586, + 33722, + 34272, + 34205, + 34352, + 34002, + 33685, + 33289, + 33200, + 32921, + 32754, + 31133, + 30829, + 31402, + 31499, + 31730, + 31824, + 32355, + 32102, + 33380, + 34317, + 34264, + 34457, + 34747, + 34281, + 34350, + 34628, + 34677, + 35083, + 35065, + 34724, + 34397, + 34621, + 34801, + 35275, + 35037, + 34668, + 34848, + 34494, + 34629, + 34758, + 34866, + 34896, + 34875, + 35032, + 34691, + 33964, + 34313, + 34304, + 34492, + 34708, + 35029, + 34671, + 34912, + 34562, + 33474, + 33572, + 33733, + 34046, + 33895, + 33957, + 34294, + 34147, + 34274, + 33961, + 36381, + 36946, + 36874, + 37104, + 38534, + 38598, + 38594, + 38612, + 38745, + 38670, + 38999, + 39871, + 39174, + 39332, + 39315, + 39466, + 39942, + 39609, + 39871, + 39883, + 39660, + 39618, + 39785, + 39203, + 39897, + 39470, + 39324, + 39646, + 39505, + 39508, + 39073, + 39075, + 39197, + 38918, + 39146, + 39717, + 38490, + 38344, + 38180, + 38027, + 38371, + 37611, + 37607, + 37546, + 37625, + 37708, + 37958, + 37837, + 37233, + 37389, + 36394, + 36041, + 35832, + 35681, + 35758, + 35704, + 35772, + 35778, + 35850, + 35719, + 35838, + 36144, + 36145, + 36064, + 36085, + 36113, + 36049, + 36183, + 36112, + 35446, + 35147, + 35594, + 35542, + 35720, + 35417, + 35743, + 35331, + 35318, + 34863, + 34993, + 34869, + 35160, + 35357, + 35842, + 35913, + 35479, + 34388, + 34901, + 34983, + 35266, + 35575, + 35321, + 35486, + 35801, + 35808, + 35589, + 35532, + 35745, + 35584, + 35351, + 35544, + 35864, + 37785, + 37921, + 38280, + 38346, + 38407, + 37972, + 38478, + 38145, + 38160, + 37796, + 37858, + 37129, + 36912, + 35491, + 35500, + 35505, + 35446, + 35182, + 35606, + 35640, + 35899, + 35798, + 35828, + 35165, + 35214, + 35300, + 35617, + 35604, + 36226, + 35747, + 35669, + 35587, + 35582, + 35606, + 35507, + 35935, + 35855, + 35900, + 35840, + 35897, + 35838, + 35818, + 35850, + 35834, + 35820, + 35875, + 35605, + 35760, + 35946, + 35929, + 35790, + 35773, + 35556, + 35526, + 35676, + 35690, + 35600, + 35425, + 34601, + 34917, + 35031, + 35286, + 35747, + 35664, + 35569, + 35557, + 35505, + 35321, + 35468, + 35418, + 36256, + 35440, + 35360, + 35307, + 34951, + 35036, + 35215, + 35214, + 35548, + 35914, + 36249, + 36193, + 35848, + 36030, + 35733, + 35838, + 36090, + 36872, + 36795, + 36914, + 36975, + 36781, + 37000, + 37267, + 37282, + 37325, + 37148, + 37274, + 36904, + 36931, + 36882, + 37193, + 36771, + 36691, + 36805, + 36983, + 36910, + 36580, + 36622, + 36786, + 36642, + 36635, + 36674, + 36848, + 37077, + 37717, + 38057, + 37834, + 37775, + 37729, + 37838, + 37736, + 37618, + 37687, + 37733, + 37800, + 37973, + 37938, + 38060, + 38134, + 38151, + 37975, + 37920, + 37883, + 37894, + 37803, + 37644, + 37496, + 37386, + 37088, + 37452, + 37424, + 37351, + 37309, + 37005, + 37115, + 37186, + 37417, + 37420, + 37301, + 37338, + 37251, + 37358, + 37468, + 37240, + 37416, + 38340, + 39011, + 38472, + 38599, + 39079, + 38793, + 38646, + 38656, + 38349, + 38867, + 38401, + 38366, + 38206, + 38136, + 38221, + 38331, + 38294, + 38313, + 38737, + 38337, + 38400, + 38497, + 38953, + 38956, + 38892, + 39172, + 39266, + 39140, + 39139, + 39184, + 39948, + 39871, + 39943, + 40016, + 39982, + 39977, + 39898, + 39994, + 39863, + 40081, + 40471, + 40854, + 40294, + 40301, + 40488, + 40550, + 40387, + 40336, + 40489, + 40594, + 40603, + 40577, + 40451, + 40341, + 40294, + 40418, + 40434, + 40504, + 40585, + 40771, + 40699, + 40682, + 40784, + 40613, + 40590, + 40671, + 40840, + 40900, + 40810, + 42347, + 42669, + 42719, + 42902, + 42992, + 42907, + 42931, + 43132, + 43547, + 43179, + 43360, + 43659, + 43348, + 42838, + 43285, + 43078, + 43423, + 43048, + 43138, + 43226, + 42847, + 42690, + 42860, + 43061, + 42631, + 42768, + 42201, + 42711, + 42776, + 42558, + 42385, + 42465, + 42089, + 42248, + 42780, + 42244, + 42127, + 42195, + 42402, + 42169, + 42166, + 42333, + 42565, + 42499, + 42308, + 41857, + 41359, + 41245, + 41332, + 41130, + 40600, + 40318, + 40363, + 40750, + 40834, + 40787, + 41174, + 42130, + 42103, + 42000, + 41949, + 41920, + 42294, + 42174, + 42159, + 42217, + 42295, + 42192, + 42329, + 41893, + 41997, + 41769, + 41906, + 41474, + 41557, + 41655, + 41995, + 42034, + 42027, + 41889, + 42076, + 42032, + 42013, + 42001, + 42503, + 42060, + 41555, + 41641, + 41867, + 41842, + 41703, + 41965, + 42047, + 41524, + 41412, + 41900, + 42352, + 42465, + 42530, + 42497, + 42544, + 42514, + 42413, + 42566, + 42333, + 41930, + 42137, + 42209, + 42169, + 41742, + 41547, + 41521, + 41731, + 41578, + 41560, + 41072, + 40452, + 40504, + 40226, + 40175, + 40273, + 39609, + 39762, + 39708, + 39742, + 39874, + 39905, + 40126, + 40019, + 39789, + 40118, + 39937, + 40029, + 39980, + 40172, + 40127, + 40190, + 40281, + 40138, + 39865, + 39622, + 40142, + 39590, + 39285, + 39175, + 38893, + 38969, + 39002, + 39163, + 39102, + 39082, + 39072, + 39087, + 38831, + 39156, + 39140, + 39165, + 39370, + 39383, + 39394, + 39290, + 39232, + 39099, + 39306, + 39219, + 39370, + 39708, + 39705, + 39205, + 38740, + 38764, + 38726, + 38855, + 38817, + 38000, + 37684, + 37829, + 37333, + 37182, + 36729, + 36235, + 36318, + 36327, + 36570, + 36954, + 36989, + 36998, + 37200, + 37197, + 37046, + 36976, + 36294, + 36693, + 37042, + 36787, + 37012, + 37110, + 37006, + 37027, + 36729, + 37497, + 37934, + 37761, + 37760, + 37883, + 37784, + 38064, + 37912, + 37892, + 37824, + 37733, + 37622, + 37732, + 37024, + 36940, + 36723, + 36934, + 36941, + 36960, + 37220, + 37150, + 37271, + 37087, + 37173, + 37073, + 37396, + 37445, + 37349, + 37427, + 37551, + 37538, + 37406, + 37456, + 37422, + 37355, + 37393, + 37410, + 37109, + 37194, + 37267, + 37472, + 37365, + 37418, + 37288, + 37414, + 37303, + 37424, + 37490, + 37360, + 37464, + 37135, + 37356, + 37229, + 36724, + 36817, + 36905, + 36108, + 36088, + 36151, + 36114, + 36435, + 36436, + 37272, + 37712, + 37869, + 37851, + 37836, + 37801, + 37771, + 37661, + 37643, + 37722, + 38400, + 38424, + 38342, + 38404, + 38287, + 38434, + 38314, + 38307, + 38266, + 38299, + 38878, + 38351, + 38037, + 38146, + 37944, + 38270, + 38148, + 38471, + 38533, + 38251, + 38362, + 38336, + 39025, + 39265, + 38858, + 38293, + 38075, + 37615, + 37382, + 37443, + 37375, + 37532, + 37701, + 37325, + 37496, + 37244, + 36532, + 36493, + 36620, + 36730, + 36815, + 36722, + 36590, + 36702, + 36729, + 36671, + 36732, + 36838, + 36923, + 36907, + 36897, + 37019, + 36552, + 36599, + 36900, + 36767, + 36875, + 36835, + 36739, + 36691, + 36749, + 36564, + 36740, + 36700, + 36538, + 36322, + 36423, + 36182, + 35831, + 35981, + 36153, + 36161, + 36401, + 36783, + 37543, + 37529, + 37709, + 37754, + 37755, + 37839, + 37981, + 37806, + 37894, + 37252, + 36186, + 36082, + 35958, + 36057, + 35838, + 36013, + 36016, + 36053, + 36748, + 36798, + 36827, + 37313, + 36991, + 37007, + 36782, + 37189, + 37190, + 37192, + 37441, + 37420, + 37305, + 37832, + 37810, + 37176, + 37631, + 38342, + 38007, + 37945, + 37864, + 37867, + 37590, + 37487, + 37509, + 37018, + 36731, + 37342, + 36982, + 36592, + 36349, + 36602, + 36631, + 36733, + 36696, + 36733, + 36678, + 36678, + 36594, + 36559, + 36532, + 36361, + 36533, + 36385, + 35749, + 35999, + 36121, + 36151, + 36026, + 36076, + 36030, + 35968, + 36539, + 36433, + 35884, + 36376, + 36487, + 36576, + 36750, + 37100, + 36885, + 36845, + 36616, + 36966, + 36758, + 36501, + 36695, + 36781, + 36659, + 36566, + 36605, + 36665, + 36652, + 36581, + 36610, + 36344, + 36385, + 36183, + 35854, + 35902, + 35854, + 35991, + 36152, + 36214, + 36768, + 36936, + 37016, + 36694, + 36973, + 36979, + 37574, + 37376, + 37337, + 37274, + 37424, + 37300, + 37315, + 37209, + 37435, + 36949, + 35174, + 35085, + 34462, + 34598, + 34699, + 34495, + 34620, + 34701, + 34598, + 34452, + 33876, + 33799, + 34014, + 34027, + 34150, + 34113, + 34123, + 34122, + 33963, + 34038, + 34075, + 34106, + 34215, + 34080, + 34132, + 34059, + 34047, + 33200, + 33634, + 33010, + 32740, + 32700, + 32842, + 32798, + 32975, + 32600, + 32600, + 32316, + 32816, + 32686, + 32284, + 32323, + 31940, + 31860, + 31892, + 31767, + 31403, + 31303, + 30626, + 29633, + 29362, + 29220, + 28501, + 29243, + 29001, + 30110, + 29969, + 29815, + 29982, + 30169, + 29739, + 30073, + 29750, + 28750, + 29426, + 29220, + 29624, + 29829, + 28979, + 30016, + 29840, + 29377, + 29316, + 28385, + 27885, + 27557, + 27553, + 27672, + 26893, + 25502, + 26534, + 26399, + 27151, + 26913, + 27814, + 27533, + 27517, + 27325, + 27893, + 28339, + 29294, + 29337, + 28908, + 29338, + 29503, + 29643, + 29142, + 28478, + 28919, + 28837, + 28128, + 28520, + 28400, + 28274, + 28244, + 28314, + 27907, + 27673, + 27690, + 28458, + 28169, + 28513, + 28918, + 28766, + 28574, + 28591, + 28671, + 28615, + 29064, + 29008, + 28831, + 28795, + 29277, + 29901, + 30112, + 29551, + 29161, + 29074, + 28442, + 28421, + 28772, + 28348, + 28458, + 28480, + 28332, + 28899, + 28585, + 28654, + 29040, + 29173, + 29042, + 29217, + 28762, + 28961, + 28610, + 28505, + 28530, + 28942, + 28854, + 28570, + 28317, + 28467, + 28418, + 28446, + 28332, + 27851, + 27588, + 27625, + 27968, + 27853, + 27393, + 27414, + 27785, + 27662, + 27800, + 27603, + 28030, + 27873, + 28680, + 28419, + 28279, + 28558, + 28636, + 28789, + 28532, + 28450, + 28584, + 28566, + 28764, + 28733, + 27579, + 27440, + 27720, + 27619, + 27618, + 27582, + 27675, + 27717, + 27859, + 27747, + 27715, + 27737, + 27963, + 27821, + 27884, + 27750, + 27837, + 27888, + 27765, + 27782, + 27853, + 28479, + 28240, + 28418, + 28215, + 28282, + 28307, + 28393, + 28633, + 28493, + 28460, + 28538, + 28701, + 28486, + 28495, + 28405, + 28425, + 28143, + 27205, + 27446, + 27231, + 27381, + 27449, + 27482, + 27400, + 27375, + 27329, + 26923, + 27374, + 27335, + 27334, + 27540, + 27585, + 27778, + 28128, + 27840, + 27751, + 27877, + 27570, + 27840, + 27753, + 27831, + 27673, + 27905, + 27594, + 27746, + 27852, + 27825, + 27791, + 27252, + 27076, + 26887, + 27398, + 27650, + 27392, + 27542, + 27219, + 26846, + 27000, + 26776, + 26972, + 27082, + 26957, + 27280, + 26949, + 26443, + 26886, + 26729, + 26616, + 26625, + 26847, + 26894, + 26892, + 26842, + 26858, + 27081, + 27009, + 26889, + 27030, + 27027, + 27036, + 26917, + 27006, + 27051, + 27024, + 27015, + 27221, + 27309, + 27218, + 27225, + 27110, + 27221, + 27433, + 27633, + 28256, + 28289, + 28489, + 28509, + 28438, + 28314, + 28388, + 28470, + 28435, + 29500, + 29411, + 29336, + 29540, + 29440, + 29386, + 29377, + 29628, + 29297, + 29871, + 29927, + 29476, + 29527, + 29588, + 29781, + 29503, + 29528, + 29350, + 29543, + 29487, + 29641, + 28875, + 28306, + 28273, + 27776, + 27968, + 27986, + 27902, + 28011, + 28045, + 28006, + 28229, + 27937, + 28146, + 28092, + 28157, + 28235, + 28332, + 28444, + 28374, + 28395, + 28288, + 28138, + 27718, + 27643, + 27506, + 27523, + 27579, + 27807, + 27740, + 27591, + 27565, + 27709, + 27704, + 27720, + 27701, + 27635, + 27850, + 27750, + 27674, + 27860, + 27869, + 27709, + 27763, + 27798, + 27651, + 27662, + 27741, + 27672, + 27951, + 28023, + 28041, + 27944, + 27875, + 28742, + 29138, + 29004, + 29071, + 29247, + 29214, + 29308, + 29436, + 29237, + 29406, + 29440, + 29334, + 27718, + 27718, + 27647, + 27668, + 27678, + 27663, + 27660, + 27956, + 27990, + 28963, + 29268, + 29049, + 29080, + 28076, + 28585, + 28585, + 28497, + 28331, + 28632, + 28356, + 28312, + 28138, + 28347, + 28178, + 28105, + 28227, + 28272, + 28437, + 28476, + 28221, + 28223, + 28447, + 28465, + 28246, + 28367, + 28336, + 28125, + 28304, + 28309, + 28268, + 28190, + 28370, + 27909, + 27989, + 27664, + 27527, + 27745, + 27666, + 27751, + 27816, + 27854, + 27895, + 27642, + 27334, + 27381, + 27026, + 27114, + 27209, + 27141, + 27012, + 26810, + 26235, + 25957, + 26317, + 26155, + 26117, + 25984, + 26725, + 26750, + 26040, + 26007, + 25305, + 24576, + 24286, + 24034, + 23681, + 22947, + 22676, + 22646, + 22310, + 22526, + 22270, + 22162, + 21584, + 20091, + 21217, + 21811, + 21855, + 21361, + 21037, + 21473, + 21784, + 21455, + 21325, + 20624, + 21232, + 21211, + 20512, + 20264, + 19795, + 19254, + 20227, + 20638, + 20581, + 20275, + 20686, + 21241, + 21582, + 21426, + 21230, + 21033, + 20938, + 20410, + 20261, + 20111, + 19988, + 19890, + 19765, + 19552, + 19331, + 19796, + 19339, + 19760, + 20052, + 19910, + 19987, + 19811, + 19733, + 19589, + 19525, + 19566, + 19466, + 19442, + 19480, + 19445, + 18364, + 18275, + 18336, + 18292, + 18039, + 17710, + 17188, + 17614, + 18068, + 17558, + 17419, + 17264, + 17431, + 18098, + 18714, + 18280, + 18856, + 18523, + 19162, + 19450, + 19608, + 18851 ], - "type": "scatter", - "xaxis": "x", "yaxis": "y" }, { @@ -4415,14245 +4619,7 @@ }, "mode": "lines", "name": "Close up turn", - "x": [ - "2022-12-20 00:00:00", - "2022-12-20 02:00:00", - "2022-12-20 04:00:00", - "2022-12-20 06:00:00", - "2022-12-20 08:00:00", - "2022-12-20 10:00:00", - "2022-12-20 12:00:00", - "2022-12-20 14:00:00", - "2022-12-20 16:00:00", - "2022-12-20 18:00:00", - "2022-12-20 20:00:00", - "2022-12-20 22:00:00", - "2022-12-21 00:00:00", - "2022-12-21 02:00:00", - "2022-12-21 04:00:00", - "2022-12-21 06:00:00", - "2022-12-21 08:00:00", - "2022-12-21 10:00:00", - "2022-12-21 12:00:00", - "2022-12-21 14:00:00", - "2022-12-21 16:00:00", - "2022-12-21 18:00:00", - "2022-12-21 20:00:00", - "2022-12-21 22:00:00", - "2022-12-22 00:00:00", - "2022-12-22 02:00:00", - "2022-12-22 04:00:00", - "2022-12-22 06:00:00", - "2022-12-22 08:00:00", - "2022-12-22 10:00:00", - "2022-12-22 12:00:00", - "2022-12-22 14:00:00", - "2022-12-22 16:00:00", - "2022-12-22 18:00:00", - "2022-12-22 20:00:00", - "2022-12-22 22:00:00", - "2022-12-23 00:00:00", - "2022-12-23 02:00:00", - "2022-12-23 04:00:00", - "2022-12-23 06:00:00", - "2022-12-23 08:00:00", - "2022-12-23 10:00:00", - "2022-12-23 12:00:00", - "2022-12-23 14:00:00", - "2022-12-23 16:00:00", - "2022-12-23 18:00:00", - "2022-12-23 20:00:00", - "2022-12-23 22:00:00", - "2022-12-24 00:00:00", - "2022-12-24 02:00:00", - "2022-12-24 04:00:00", - "2022-12-24 06:00:00", - "2022-12-24 08:00:00", - "2022-12-24 10:00:00", - "2022-12-24 12:00:00", - "2022-12-24 14:00:00", - "2022-12-24 16:00:00", - "2022-12-24 18:00:00", - "2022-12-24 20:00:00", - "2022-12-24 22:00:00", - "2022-12-25 00:00:00", - "2022-12-25 02:00:00", - "2022-12-25 04:00:00", - "2022-12-25 06:00:00", - "2022-12-25 08:00:00", - "2022-12-25 10:00:00", - "2022-12-25 12:00:00", - "2022-12-25 14:00:00", - "2022-12-25 16:00:00", - "2022-12-25 18:00:00", - "2022-12-25 20:00:00", - "2022-12-25 22:00:00", - "2022-12-26 00:00:00", - "2022-12-26 02:00:00", - "2022-12-26 04:00:00", - "2022-12-26 06:00:00", - "2022-12-26 08:00:00", - "2022-12-26 10:00:00", - "2022-12-26 12:00:00", - "2022-12-26 14:00:00", - "2022-12-26 16:00:00", - "2022-12-26 18:00:00", - "2022-12-26 20:00:00", - "2022-12-26 22:00:00", - "2022-12-27 00:00:00", - "2022-12-27 02:00:00", - "2022-12-27 04:00:00", - "2022-12-27 06:00:00", - "2022-12-27 08:00:00", - "2022-12-27 10:00:00", - "2022-12-27 12:00:00", - "2022-12-27 14:00:00", - "2022-12-27 16:00:00", - "2022-12-27 18:00:00", - "2022-12-27 20:00:00", - "2022-12-27 22:00:00", - "2022-12-28 00:00:00", - "2022-12-28 02:00:00", - "2022-12-28 04:00:00", - "2022-12-28 06:00:00", - "2022-12-28 08:00:00", - "2022-12-28 10:00:00", - "2022-12-28 12:00:00", - "2022-12-28 14:00:00", - "2022-12-28 16:00:00", - "2022-12-28 18:00:00", - "2022-12-28 20:00:00", - "2022-12-28 22:00:00", - "2022-12-29 00:00:00", - "2022-12-29 02:00:00", - "2022-12-29 04:00:00", - "2022-12-29 06:00:00", - "2022-12-29 08:00:00", - "2022-12-29 10:00:00", - "2022-12-29 12:00:00", - "2022-12-29 14:00:00", - "2022-12-29 16:00:00", - "2022-12-29 18:00:00", - "2022-12-29 20:00:00", - "2022-12-29 22:00:00", - "2022-12-30 00:00:00", - "2022-12-30 02:00:00", - "2022-12-30 04:00:00", - "2022-12-30 06:00:00", - "2022-12-30 08:00:00", - "2022-12-30 10:00:00", - "2022-12-30 12:00:00", - "2022-12-30 14:00:00", - "2022-12-30 16:00:00", - "2022-12-30 18:00:00", - "2022-12-30 20:00:00", - "2022-12-30 22:00:00", - "2022-12-31 00:00:00", - "2022-12-31 02:00:00", - "2022-12-31 04:00:00", - "2022-12-31 06:00:00", - "2022-12-31 08:00:00", - "2022-12-31 10:00:00", - "2022-12-31 12:00:00", - "2022-12-31 14:00:00", - "2022-12-31 16:00:00", - "2022-12-31 18:00:00", - "2022-12-31 20:00:00", - "2022-12-31 22:00:00", - "2023-01-01 00:00:00", - "2023-01-01 02:00:00", - "2023-01-01 04:00:00", - "2023-01-01 06:00:00", - "2023-01-01 08:00:00", - "2023-01-01 10:00:00", - "2023-01-01 12:00:00", - "2023-01-01 14:00:00", - "2023-01-01 16:00:00", - "2023-01-01 18:00:00", - "2023-01-01 20:00:00", - "2023-01-01 22:00:00", - "2023-01-02 00:00:00", - "2023-01-02 02:00:00", - "2023-01-02 04:00:00", - "2023-01-02 06:00:00", - "2023-01-02 08:00:00", - "2023-01-02 10:00:00", - "2023-01-02 12:00:00", - "2023-01-02 14:00:00", - "2023-01-02 16:00:00", - "2023-01-02 18:00:00", - "2023-01-02 20:00:00", - "2023-01-02 22:00:00", - "2023-01-03 00:00:00", - "2023-01-03 02:00:00", - "2023-01-03 04:00:00", - "2023-01-03 06:00:00", - "2023-01-03 08:00:00", - "2023-01-03 10:00:00", - "2023-01-03 12:00:00", - "2023-01-03 14:00:00", - "2023-01-03 16:00:00", - "2023-01-03 18:00:00", - "2023-01-03 20:00:00", - "2023-01-03 22:00:00", - "2023-01-04 00:00:00", - "2023-01-04 02:00:00", - "2023-01-04 04:00:00", - "2023-01-04 06:00:00", - "2023-01-04 08:00:00", - "2023-01-04 10:00:00", - "2023-01-04 12:00:00", - "2023-01-04 14:00:00", - "2023-01-04 16:00:00", - "2023-01-04 18:00:00", - "2023-01-04 20:00:00", - "2023-01-04 22:00:00", - "2023-01-05 00:00:00", - "2023-01-05 02:00:00", - "2023-01-05 04:00:00", - "2023-01-05 06:00:00", - "2023-01-05 08:00:00", - "2023-01-05 10:00:00", - "2023-01-05 12:00:00", - "2023-01-05 14:00:00", - "2023-01-05 16:00:00", - "2023-01-05 18:00:00", - "2023-01-05 20:00:00", - "2023-01-05 22:00:00", - "2023-01-06 00:00:00", - "2023-01-06 02:00:00", - "2023-01-06 04:00:00", - "2023-01-06 06:00:00", - "2023-01-06 08:00:00", - "2023-01-06 10:00:00", - "2023-01-06 12:00:00", - "2023-01-06 14:00:00", - "2023-01-06 16:00:00", - "2023-01-06 18:00:00", - "2023-01-06 20:00:00", - "2023-01-06 22:00:00", - "2023-01-07 00:00:00", - "2023-01-07 02:00:00", - "2023-01-07 04:00:00", - "2023-01-07 06:00:00", - "2023-01-07 08:00:00", - "2023-01-07 10:00:00", - "2023-01-07 12:00:00", - "2023-01-07 14:00:00", - "2023-01-07 16:00:00", - "2023-01-07 18:00:00", - "2023-01-07 20:00:00", - "2023-01-07 22:00:00", - "2023-01-08 00:00:00", - "2023-01-08 02:00:00", - "2023-01-08 04:00:00", - "2023-01-08 06:00:00", - "2023-01-08 08:00:00", - "2023-01-08 10:00:00", - "2023-01-08 12:00:00", - "2023-01-08 14:00:00", - "2023-01-08 16:00:00", - "2023-01-08 18:00:00", - "2023-01-08 20:00:00", - "2023-01-08 22:00:00", - "2023-01-09 00:00:00", - "2023-01-09 02:00:00", - "2023-01-09 04:00:00", - "2023-01-09 06:00:00", - "2023-01-09 08:00:00", - "2023-01-09 10:00:00", - "2023-01-09 12:00:00", - "2023-01-09 14:00:00", - "2023-01-09 16:00:00", - "2023-01-09 18:00:00", - "2023-01-09 20:00:00", - "2023-01-09 22:00:00", - "2023-01-10 00:00:00", - "2023-01-10 02:00:00", - "2023-01-10 04:00:00", - "2023-01-10 06:00:00", - "2023-01-10 08:00:00", - "2023-01-10 10:00:00", - "2023-01-10 12:00:00", - "2023-01-10 14:00:00", - "2023-01-10 16:00:00", - "2023-01-10 18:00:00", - "2023-01-10 20:00:00", - "2023-01-10 22:00:00", - "2023-01-11 00:00:00", - "2023-01-11 02:00:00", - "2023-01-11 04:00:00", - "2023-01-11 06:00:00", - "2023-01-11 08:00:00", - "2023-01-11 10:00:00", - "2023-01-11 12:00:00", - "2023-01-11 14:00:00", - "2023-01-11 16:00:00", - "2023-01-11 18:00:00", - "2023-01-11 20:00:00", - "2023-01-11 22:00:00", - "2023-01-12 00:00:00", - "2023-01-12 02:00:00", - "2023-01-12 04:00:00", - "2023-01-12 06:00:00", - "2023-01-12 08:00:00", - "2023-01-12 10:00:00", - "2023-01-12 12:00:00", - "2023-01-12 14:00:00", - "2023-01-12 16:00:00", - "2023-01-12 18:00:00", - "2023-01-12 20:00:00", - "2023-01-12 22:00:00", - "2023-01-13 00:00:00", - "2023-01-13 02:00:00", - "2023-01-13 04:00:00", - "2023-01-13 06:00:00", - "2023-01-13 08:00:00", - "2023-01-13 10:00:00", - "2023-01-13 12:00:00", - "2023-01-13 14:00:00", - "2023-01-13 16:00:00", - "2023-01-13 18:00:00", - "2023-01-13 20:00:00", - "2023-01-13 22:00:00", - "2023-01-14 00:00:00", - "2023-01-14 02:00:00", - "2023-01-14 04:00:00", - "2023-01-14 06:00:00", - "2023-01-14 08:00:00", - "2023-01-14 10:00:00", - "2023-01-14 12:00:00", - "2023-01-14 14:00:00", - "2023-01-14 16:00:00", - "2023-01-14 18:00:00", - "2023-01-14 20:00:00", - "2023-01-14 22:00:00", - "2023-01-15 00:00:00", - "2023-01-15 02:00:00", - "2023-01-15 04:00:00", - "2023-01-15 06:00:00", - "2023-01-15 08:00:00", - "2023-01-15 10:00:00", - "2023-01-15 12:00:00", - "2023-01-15 14:00:00", - "2023-01-15 16:00:00", - "2023-01-15 18:00:00", - "2023-01-15 20:00:00", - "2023-01-15 22:00:00", - "2023-01-16 00:00:00", - "2023-01-16 02:00:00", - "2023-01-16 04:00:00", - "2023-01-16 06:00:00", - "2023-01-16 08:00:00", - "2023-01-16 10:00:00", - "2023-01-16 12:00:00", - "2023-01-16 14:00:00", - "2023-01-16 16:00:00", - "2023-01-16 18:00:00", - "2023-01-16 20:00:00", - "2023-01-16 22:00:00", - "2023-01-17 00:00:00", - "2023-01-17 02:00:00", - "2023-01-17 04:00:00", - "2023-01-17 06:00:00", - "2023-01-17 08:00:00", - "2023-01-17 10:00:00", - "2023-01-17 12:00:00", - "2023-01-17 14:00:00", - "2023-01-17 16:00:00", - "2023-01-17 18:00:00", - "2023-01-17 20:00:00", - "2023-01-17 22:00:00", - "2023-01-18 00:00:00", - "2023-01-18 02:00:00", - "2023-01-18 04:00:00", - "2023-01-18 06:00:00", - "2023-01-18 08:00:00", - "2023-01-18 10:00:00", - "2023-01-18 12:00:00", - "2023-01-18 14:00:00", - "2023-01-18 16:00:00", - "2023-01-18 18:00:00", - "2023-01-18 20:00:00", - "2023-01-18 22:00:00", - "2023-01-19 00:00:00", - "2023-01-19 02:00:00", - "2023-01-19 04:00:00", - "2023-01-19 06:00:00", - "2023-01-19 08:00:00", - "2023-01-19 10:00:00", - "2023-01-19 12:00:00", - "2023-01-19 14:00:00", - "2023-01-19 16:00:00", - "2023-01-19 18:00:00", - "2023-01-19 20:00:00", - "2023-01-19 22:00:00", - "2023-01-20 00:00:00", - "2023-01-20 02:00:00", - "2023-01-20 04:00:00", - "2023-01-20 06:00:00", - "2023-01-20 08:00:00", - "2023-01-20 10:00:00", - "2023-01-20 12:00:00", - "2023-01-20 14:00:00", - "2023-01-20 16:00:00", - "2023-01-20 18:00:00", - "2023-01-20 20:00:00", - "2023-01-20 22:00:00", - "2023-01-21 00:00:00", - "2023-01-21 02:00:00", - "2023-01-21 04:00:00", - "2023-01-21 06:00:00", - "2023-01-21 08:00:00", - "2023-01-21 10:00:00", - "2023-01-21 12:00:00", - "2023-01-21 14:00:00", - "2023-01-21 16:00:00", - "2023-01-21 18:00:00", - "2023-01-21 20:00:00", - "2023-01-21 22:00:00", - "2023-01-22 00:00:00", - "2023-01-22 02:00:00", - "2023-01-22 04:00:00", - "2023-01-22 06:00:00", - "2023-01-22 08:00:00", - "2023-01-22 10:00:00", - "2023-01-22 12:00:00", - "2023-01-22 14:00:00", - "2023-01-22 16:00:00", - "2023-01-22 18:00:00", - "2023-01-22 20:00:00", - "2023-01-22 22:00:00", - "2023-01-23 00:00:00", - "2023-01-23 02:00:00", - "2023-01-23 04:00:00", - "2023-01-23 06:00:00", - "2023-01-23 08:00:00", - "2023-01-23 10:00:00", - "2023-01-23 12:00:00", - "2023-01-23 14:00:00", - "2023-01-23 16:00:00", - "2023-01-23 18:00:00", - "2023-01-23 20:00:00", - "2023-01-23 22:00:00", - "2023-01-24 00:00:00", - "2023-01-24 02:00:00", - "2023-01-24 04:00:00", - "2023-01-24 06:00:00", - "2023-01-24 08:00:00", - "2023-01-24 10:00:00", - "2023-01-24 12:00:00", - "2023-01-24 14:00:00", - "2023-01-24 16:00:00", - "2023-01-24 18:00:00", - "2023-01-24 20:00:00", - "2023-01-24 22:00:00", - "2023-01-25 00:00:00", - "2023-01-25 02:00:00", - "2023-01-25 04:00:00", - "2023-01-25 06:00:00", - "2023-01-25 08:00:00", - "2023-01-25 10:00:00", - "2023-01-25 12:00:00", - "2023-01-25 14:00:00", - "2023-01-25 16:00:00", - "2023-01-25 18:00:00", - "2023-01-25 20:00:00", - "2023-01-25 22:00:00", - "2023-01-26 00:00:00", - "2023-01-26 02:00:00", - "2023-01-26 04:00:00", - "2023-01-26 06:00:00", - "2023-01-26 08:00:00", - "2023-01-26 10:00:00", - "2023-01-26 12:00:00", - "2023-01-26 14:00:00", - "2023-01-26 16:00:00", - "2023-01-26 18:00:00", - "2023-01-26 20:00:00", - "2023-01-26 22:00:00", - "2023-01-27 00:00:00", - "2023-01-27 02:00:00", - "2023-01-27 04:00:00", - "2023-01-27 06:00:00", - "2023-01-27 08:00:00", - "2023-01-27 10:00:00", - "2023-01-27 12:00:00", - "2023-01-27 14:00:00", - "2023-01-27 16:00:00", - "2023-01-27 18:00:00", - "2023-01-27 20:00:00", - "2023-01-27 22:00:00", - "2023-01-28 00:00:00", - "2023-01-28 02:00:00", - "2023-01-28 04:00:00", - "2023-01-28 06:00:00", - "2023-01-28 08:00:00", - "2023-01-28 10:00:00", - "2023-01-28 12:00:00", - "2023-01-28 14:00:00", - "2023-01-28 16:00:00", - "2023-01-28 18:00:00", - "2023-01-28 20:00:00", - "2023-01-28 22:00:00", - "2023-01-29 00:00:00", - "2023-01-29 02:00:00", - "2023-01-29 04:00:00", - "2023-01-29 06:00:00", - "2023-01-29 08:00:00", - "2023-01-29 10:00:00", - "2023-01-29 12:00:00", - "2023-01-29 14:00:00", - "2023-01-29 16:00:00", - "2023-01-29 18:00:00", - "2023-01-29 20:00:00", - "2023-01-29 22:00:00", - "2023-01-30 00:00:00", - "2023-01-30 02:00:00", - "2023-01-30 04:00:00", - "2023-01-30 06:00:00", - "2023-01-30 08:00:00", - "2023-01-30 10:00:00", - "2023-01-30 12:00:00", - "2023-01-30 14:00:00", - "2023-01-30 16:00:00", - "2023-01-30 18:00:00", - "2023-01-30 20:00:00", - "2023-01-30 22:00:00", - "2023-01-31 00:00:00", - "2023-01-31 02:00:00", - "2023-01-31 04:00:00", - "2023-01-31 06:00:00", - "2023-01-31 08:00:00", - "2023-01-31 10:00:00", - "2023-01-31 12:00:00", - "2023-01-31 14:00:00", - "2023-01-31 16:00:00", - "2023-01-31 18:00:00", - "2023-01-31 20:00:00", - "2023-01-31 22:00:00", - "2023-02-01 00:00:00", - "2023-02-01 02:00:00", - "2023-02-01 04:00:00", - "2023-02-01 06:00:00", - "2023-02-01 08:00:00", - "2023-02-01 10:00:00", - "2023-02-01 12:00:00", - "2023-02-01 14:00:00", - "2023-02-01 16:00:00", - "2023-02-01 18:00:00", - "2023-02-01 20:00:00", - "2023-02-01 22:00:00", - "2023-02-02 00:00:00", - "2023-02-02 02:00:00", - "2023-02-02 04:00:00", - "2023-02-02 06:00:00", - "2023-02-02 08:00:00", - "2023-02-02 10:00:00", - "2023-02-02 12:00:00", - "2023-02-02 14:00:00", - "2023-02-02 16:00:00", - "2023-02-02 18:00:00", - "2023-02-02 20:00:00", - "2023-02-02 22:00:00", - "2023-02-03 00:00:00", - "2023-02-03 02:00:00", - "2023-02-03 04:00:00", - "2023-02-03 06:00:00", - "2023-02-03 08:00:00", - "2023-02-03 10:00:00", - "2023-02-03 12:00:00", - "2023-02-03 14:00:00", - "2023-02-03 16:00:00", - "2023-02-03 18:00:00", - "2023-02-03 20:00:00", - "2023-02-03 22:00:00", - "2023-02-04 00:00:00", - "2023-02-04 02:00:00", - "2023-02-04 04:00:00", - "2023-02-04 06:00:00", - "2023-02-04 08:00:00", - "2023-02-04 10:00:00", - "2023-02-04 12:00:00", - "2023-02-04 14:00:00", - "2023-02-04 16:00:00", - "2023-02-04 18:00:00", - "2023-02-04 20:00:00", - "2023-02-04 22:00:00", - "2023-02-05 00:00:00", - "2023-02-05 02:00:00", - "2023-02-05 04:00:00", - "2023-02-05 06:00:00", - "2023-02-05 08:00:00", - "2023-02-05 10:00:00", - "2023-02-05 12:00:00", - "2023-02-05 14:00:00", - "2023-02-05 16:00:00", - "2023-02-05 18:00:00", - "2023-02-05 20:00:00", - "2023-02-05 22:00:00", - "2023-02-06 00:00:00", - "2023-02-06 02:00:00", - "2023-02-06 04:00:00", - "2023-02-06 06:00:00", - "2023-02-06 08:00:00", - "2023-02-06 10:00:00", - "2023-02-06 12:00:00", - "2023-02-06 14:00:00", - "2023-02-06 16:00:00", - "2023-02-06 18:00:00", - "2023-02-06 20:00:00", - "2023-02-06 22:00:00", - "2023-02-07 00:00:00", - "2023-02-07 02:00:00", - "2023-02-07 04:00:00", - "2023-02-07 06:00:00", - "2023-02-07 08:00:00", - "2023-02-07 10:00:00", - "2023-02-07 12:00:00", - "2023-02-07 14:00:00", - "2023-02-07 16:00:00", - "2023-02-07 18:00:00", - "2023-02-07 20:00:00", - "2023-02-07 22:00:00", - "2023-02-08 00:00:00", - "2023-02-08 02:00:00", - "2023-02-08 04:00:00", - "2023-02-08 06:00:00", - "2023-02-08 08:00:00", - "2023-02-08 10:00:00", - "2023-02-08 12:00:00", - "2023-02-08 14:00:00", - "2023-02-08 16:00:00", - "2023-02-08 18:00:00", - "2023-02-08 20:00:00", - "2023-02-08 22:00:00", - "2023-02-09 00:00:00", - "2023-02-09 02:00:00", - "2023-02-09 04:00:00", - "2023-02-09 06:00:00", - "2023-02-09 08:00:00", - "2023-02-09 10:00:00", - "2023-02-09 12:00:00", - "2023-02-09 14:00:00", - "2023-02-09 16:00:00", - "2023-02-09 18:00:00", - "2023-02-09 20:00:00", - "2023-02-09 22:00:00", - "2023-02-10 00:00:00", - "2023-02-10 02:00:00", - "2023-02-10 04:00:00", - "2023-02-10 06:00:00", - "2023-02-10 08:00:00", - "2023-02-10 10:00:00", - "2023-02-10 12:00:00", - "2023-02-10 14:00:00", - "2023-02-10 16:00:00", - "2023-02-10 18:00:00", - "2023-02-10 20:00:00", - "2023-02-10 22:00:00", - "2023-02-11 00:00:00", - "2023-02-11 02:00:00", - "2023-02-11 04:00:00", - "2023-02-11 06:00:00", - "2023-02-11 08:00:00", - "2023-02-11 10:00:00", - "2023-02-11 12:00:00", - "2023-02-11 14:00:00", - "2023-02-11 16:00:00", - "2023-02-11 18:00:00", - "2023-02-11 20:00:00", - "2023-02-11 22:00:00", - "2023-02-12 00:00:00", - "2023-02-12 02:00:00", - "2023-02-12 04:00:00", - "2023-02-12 06:00:00", - "2023-02-12 08:00:00", - "2023-02-12 10:00:00", - "2023-02-12 12:00:00", - "2023-02-12 14:00:00", - "2023-02-12 16:00:00", - "2023-02-12 18:00:00", - "2023-02-12 20:00:00", - "2023-02-12 22:00:00", - "2023-02-13 00:00:00", - "2023-02-13 02:00:00", - "2023-02-13 04:00:00", - "2023-02-13 06:00:00", - "2023-02-13 08:00:00", - "2023-02-13 10:00:00", - "2023-02-13 12:00:00", - "2023-02-13 14:00:00", - "2023-02-13 16:00:00", - "2023-02-13 18:00:00", - "2023-02-13 20:00:00", - "2023-02-13 22:00:00", - "2023-02-14 00:00:00", - "2023-02-14 02:00:00", - "2023-02-14 04:00:00", - "2023-02-14 06:00:00", - "2023-02-14 08:00:00", - "2023-02-14 10:00:00", - "2023-02-14 12:00:00", - "2023-02-14 14:00:00", - "2023-02-14 16:00:00", - "2023-02-14 18:00:00", - "2023-02-14 20:00:00", - "2023-02-14 22:00:00", - "2023-02-15 00:00:00", - "2023-02-15 02:00:00", - "2023-02-15 04:00:00", - "2023-02-15 06:00:00", - "2023-02-15 08:00:00", - "2023-02-15 10:00:00", - "2023-02-15 12:00:00", - "2023-02-15 14:00:00", - "2023-02-15 16:00:00", - "2023-02-15 18:00:00", - "2023-02-15 20:00:00", - "2023-02-15 22:00:00", - "2023-02-16 00:00:00", - "2023-02-16 02:00:00", - "2023-02-16 04:00:00", - "2023-02-16 06:00:00", - "2023-02-16 08:00:00", - "2023-02-16 10:00:00", - "2023-02-16 12:00:00", - "2023-02-16 14:00:00", - "2023-02-16 16:00:00", - "2023-02-16 18:00:00", - "2023-02-16 20:00:00", - "2023-02-16 22:00:00", - "2023-02-17 00:00:00", - "2023-02-17 02:00:00", - "2023-02-17 04:00:00", - "2023-02-17 06:00:00", - "2023-02-17 08:00:00", - "2023-02-17 10:00:00", - "2023-02-17 12:00:00", - "2023-02-17 14:00:00", - "2023-02-17 16:00:00", - "2023-02-17 18:00:00", - "2023-02-17 20:00:00", - "2023-02-17 22:00:00", - "2023-02-18 00:00:00", - "2023-02-18 02:00:00", - "2023-02-18 04:00:00", - "2023-02-18 06:00:00", - "2023-02-18 08:00:00", - "2023-02-18 10:00:00", - "2023-02-18 12:00:00", - "2023-02-18 14:00:00", - "2023-02-18 16:00:00", - "2023-02-18 18:00:00", - "2023-02-18 20:00:00", - "2023-02-18 22:00:00", - "2023-02-19 00:00:00", - "2023-02-19 02:00:00", - "2023-02-19 04:00:00", - "2023-02-19 06:00:00", - "2023-02-19 08:00:00", - "2023-02-19 10:00:00", - "2023-02-19 12:00:00", - "2023-02-19 14:00:00", - "2023-02-19 16:00:00", - "2023-02-19 18:00:00", - "2023-02-19 20:00:00", - "2023-02-19 22:00:00", - "2023-02-20 00:00:00", - "2023-02-20 02:00:00", - "2023-02-20 04:00:00", - "2023-02-20 06:00:00", - "2023-02-20 08:00:00", - "2023-02-20 10:00:00", - "2023-02-20 12:00:00", - "2023-02-20 14:00:00", - "2023-02-20 16:00:00", - "2023-02-20 18:00:00", - "2023-02-20 20:00:00", - "2023-02-20 22:00:00", - "2023-02-21 00:00:00", - "2023-02-21 02:00:00", - "2023-02-21 04:00:00", - "2023-02-21 06:00:00", - "2023-02-21 08:00:00", - "2023-02-21 10:00:00", - "2023-02-21 12:00:00", - "2023-02-21 14:00:00", - "2023-02-21 16:00:00", - "2023-02-21 18:00:00", - "2023-02-21 20:00:00", - "2023-02-21 22:00:00", - "2023-02-22 00:00:00", - "2023-02-22 02:00:00", - "2023-02-22 04:00:00", - "2023-02-22 06:00:00", - "2023-02-22 08:00:00", - "2023-02-22 10:00:00", - "2023-02-22 12:00:00", - "2023-02-22 14:00:00", - "2023-02-22 16:00:00", - "2023-02-22 18:00:00", - "2023-02-22 20:00:00", - "2023-02-22 22:00:00", - "2023-02-23 00:00:00", - "2023-02-23 02:00:00", - "2023-02-23 04:00:00", - "2023-02-23 06:00:00", - "2023-02-23 08:00:00", - "2023-02-23 10:00:00", - "2023-02-23 12:00:00", - "2023-02-23 14:00:00", - "2023-02-23 16:00:00", - "2023-02-23 18:00:00", - "2023-02-23 20:00:00", - "2023-02-23 22:00:00", - "2023-02-24 00:00:00", - "2023-02-24 02:00:00", - "2023-02-24 04:00:00", - "2023-02-24 06:00:00", - "2023-02-24 08:00:00", - "2023-02-24 10:00:00", - "2023-02-24 12:00:00", - "2023-02-24 14:00:00", - "2023-02-24 16:00:00", - "2023-02-24 18:00:00", - "2023-02-24 20:00:00", - "2023-02-24 22:00:00", - "2023-02-25 00:00:00", - "2023-02-25 02:00:00", - "2023-02-25 04:00:00", - "2023-02-25 06:00:00", - "2023-02-25 08:00:00", - "2023-02-25 10:00:00", - "2023-02-25 12:00:00", - "2023-02-25 14:00:00", - "2023-02-25 16:00:00", - "2023-02-25 18:00:00", - "2023-02-25 20:00:00", - "2023-02-25 22:00:00", - "2023-02-26 00:00:00", - "2023-02-26 02:00:00", - "2023-02-26 04:00:00", - "2023-02-26 06:00:00", - "2023-02-26 08:00:00", - "2023-02-26 10:00:00", - "2023-02-26 12:00:00", - "2023-02-26 14:00:00", - "2023-02-26 16:00:00", - "2023-02-26 18:00:00", - "2023-02-26 20:00:00", - "2023-02-26 22:00:00", - "2023-02-27 00:00:00", - "2023-02-27 02:00:00", - "2023-02-27 04:00:00", - "2023-02-27 06:00:00", - "2023-02-27 08:00:00", - "2023-02-27 10:00:00", - "2023-02-27 12:00:00", - "2023-02-27 14:00:00", - "2023-02-27 16:00:00", - "2023-02-27 18:00:00", - "2023-02-27 20:00:00", - "2023-02-27 22:00:00", - "2023-02-28 00:00:00", - "2023-02-28 02:00:00", - "2023-02-28 04:00:00", - "2023-02-28 06:00:00", - "2023-02-28 08:00:00", - "2023-02-28 10:00:00", - "2023-02-28 12:00:00", - "2023-02-28 14:00:00", - "2023-02-28 16:00:00", - "2023-02-28 18:00:00", - "2023-02-28 20:00:00", - "2023-02-28 22:00:00", - "2023-03-01 00:00:00", - "2023-03-01 02:00:00", - "2023-03-01 04:00:00", - "2023-03-01 06:00:00", - "2023-03-01 08:00:00", - "2023-03-01 10:00:00", - "2023-03-01 12:00:00", - "2023-03-01 14:00:00", - "2023-03-01 16:00:00", - "2023-03-01 18:00:00", - "2023-03-01 20:00:00", - "2023-03-01 22:00:00", - "2023-03-02 00:00:00", - "2023-03-02 02:00:00", - "2023-03-02 04:00:00", - "2023-03-02 06:00:00", - "2023-03-02 08:00:00", - "2023-03-02 10:00:00", - "2023-03-02 12:00:00", - "2023-03-02 14:00:00", - "2023-03-02 16:00:00", - "2023-03-02 18:00:00", - "2023-03-02 20:00:00", - "2023-03-02 22:00:00", - "2023-03-03 00:00:00", - "2023-03-03 02:00:00", - "2023-03-03 04:00:00", - "2023-03-03 06:00:00", - "2023-03-03 08:00:00", - "2023-03-03 10:00:00", - "2023-03-03 12:00:00", - "2023-03-03 14:00:00", - "2023-03-03 16:00:00", - "2023-03-03 18:00:00", - "2023-03-03 20:00:00", - "2023-03-03 22:00:00", - "2023-03-04 00:00:00", - "2023-03-04 02:00:00", - "2023-03-04 04:00:00", - "2023-03-04 06:00:00", - "2023-03-04 08:00:00", - "2023-03-04 10:00:00", - "2023-03-04 12:00:00", - "2023-03-04 14:00:00", - "2023-03-04 16:00:00", - "2023-03-04 18:00:00", - "2023-03-04 20:00:00", - "2023-03-04 22:00:00", - "2023-03-05 00:00:00", - "2023-03-05 02:00:00", - "2023-03-05 04:00:00", - "2023-03-05 06:00:00", - "2023-03-05 08:00:00", - "2023-03-05 10:00:00", - "2023-03-05 12:00:00", - "2023-03-05 14:00:00", - "2023-03-05 16:00:00", - "2023-03-05 18:00:00", - "2023-03-05 20:00:00", - "2023-03-05 22:00:00", - "2023-03-06 00:00:00", - "2023-03-06 02:00:00", - "2023-03-06 04:00:00", - "2023-03-06 06:00:00", - "2023-03-06 08:00:00", - "2023-03-06 10:00:00", - "2023-03-06 12:00:00", - "2023-03-06 14:00:00", - "2023-03-06 16:00:00", - "2023-03-06 18:00:00", - "2023-03-06 20:00:00", - "2023-03-06 22:00:00", - "2023-03-07 00:00:00", - "2023-03-07 02:00:00", - "2023-03-07 04:00:00", - "2023-03-07 06:00:00", - "2023-03-07 08:00:00", - "2023-03-07 10:00:00", - "2023-03-07 12:00:00", - "2023-03-07 14:00:00", - "2023-03-07 16:00:00", - "2023-03-07 18:00:00", - "2023-03-07 20:00:00", - "2023-03-07 22:00:00", - "2023-03-08 00:00:00", - "2023-03-08 02:00:00", - "2023-03-08 04:00:00", - "2023-03-08 06:00:00", - "2023-03-08 08:00:00", - "2023-03-08 10:00:00", - "2023-03-08 12:00:00", - "2023-03-08 14:00:00", - "2023-03-08 16:00:00", - "2023-03-08 18:00:00", - "2023-03-08 20:00:00", - "2023-03-08 22:00:00", - "2023-03-09 00:00:00", - "2023-03-09 02:00:00", - "2023-03-09 04:00:00", - "2023-03-09 06:00:00", - "2023-03-09 08:00:00", - "2023-03-09 10:00:00", - "2023-03-09 12:00:00", - "2023-03-09 14:00:00", - "2023-03-09 16:00:00", - "2023-03-09 18:00:00", - "2023-03-09 20:00:00", - "2023-03-09 22:00:00", - "2023-03-10 00:00:00", - "2023-03-10 02:00:00", - "2023-03-10 04:00:00", - "2023-03-10 06:00:00", - "2023-03-10 08:00:00", - "2023-03-10 10:00:00", - "2023-03-10 12:00:00", - "2023-03-10 14:00:00", - "2023-03-10 16:00:00", - "2023-03-10 18:00:00", - "2023-03-10 20:00:00", - "2023-03-10 22:00:00", - "2023-03-11 00:00:00", - "2023-03-11 02:00:00", - "2023-03-11 04:00:00", - "2023-03-11 06:00:00", - "2023-03-11 08:00:00", - "2023-03-11 10:00:00", - "2023-03-11 12:00:00", - "2023-03-11 14:00:00", - "2023-03-11 16:00:00", - "2023-03-11 18:00:00", - "2023-03-11 20:00:00", - "2023-03-11 22:00:00", - "2023-03-12 00:00:00", - "2023-03-12 02:00:00", - "2023-03-12 04:00:00", - "2023-03-12 06:00:00", - "2023-03-12 08:00:00", - "2023-03-12 10:00:00", - "2023-03-12 12:00:00", - "2023-03-12 14:00:00", - "2023-03-12 16:00:00", - "2023-03-12 18:00:00", - "2023-03-12 20:00:00", - "2023-03-12 22:00:00", - "2023-03-13 00:00:00", - "2023-03-13 02:00:00", - "2023-03-13 04:00:00", - "2023-03-13 06:00:00", - "2023-03-13 08:00:00", - "2023-03-13 10:00:00", - "2023-03-13 12:00:00", - "2023-03-13 14:00:00", - "2023-03-13 16:00:00", - "2023-03-13 18:00:00", - "2023-03-13 20:00:00", - "2023-03-13 22:00:00", - "2023-03-14 00:00:00", - "2023-03-14 02:00:00", - "2023-03-14 04:00:00", - "2023-03-14 06:00:00", - "2023-03-14 08:00:00", - "2023-03-14 10:00:00", - "2023-03-14 12:00:00", - "2023-03-14 14:00:00", - "2023-03-14 16:00:00", - "2023-03-14 18:00:00", - "2023-03-14 20:00:00", - "2023-03-14 22:00:00", - "2023-03-15 00:00:00", - "2023-03-15 02:00:00", - "2023-03-15 04:00:00", - "2023-03-15 06:00:00", - "2023-03-15 08:00:00", - "2023-03-15 10:00:00", - "2023-03-15 12:00:00", - "2023-03-15 14:00:00", - "2023-03-15 16:00:00", - "2023-03-15 18:00:00", - "2023-03-15 20:00:00", - "2023-03-15 22:00:00", - "2023-03-16 00:00:00", - "2023-03-16 02:00:00", - "2023-03-16 04:00:00", - "2023-03-16 06:00:00", - "2023-03-16 08:00:00", - "2023-03-16 10:00:00", - "2023-03-16 12:00:00", - "2023-03-16 14:00:00", - "2023-03-16 16:00:00", - "2023-03-16 18:00:00", - "2023-03-16 20:00:00", - "2023-03-16 22:00:00", - "2023-03-17 00:00:00", - "2023-03-17 02:00:00", - "2023-03-17 04:00:00", - "2023-03-17 06:00:00", - "2023-03-17 08:00:00", - "2023-03-17 10:00:00", - "2023-03-17 12:00:00", - "2023-03-17 14:00:00", - "2023-03-17 16:00:00", - "2023-03-17 18:00:00", - "2023-03-17 20:00:00", - "2023-03-17 22:00:00", - "2023-03-18 00:00:00", - "2023-03-18 02:00:00", - "2023-03-18 04:00:00", - "2023-03-18 06:00:00", - "2023-03-18 08:00:00", - "2023-03-18 10:00:00", - "2023-03-18 12:00:00", - "2023-03-18 14:00:00", - "2023-03-18 16:00:00", - "2023-03-18 18:00:00", - "2023-03-18 20:00:00", - "2023-03-18 22:00:00", - "2023-03-19 00:00:00", - "2023-03-19 02:00:00", - "2023-03-19 04:00:00", - "2023-03-19 06:00:00", - "2023-03-19 08:00:00", - "2023-03-19 10:00:00", - "2023-03-19 12:00:00", - "2023-03-19 14:00:00", - "2023-03-19 16:00:00", - "2023-03-19 18:00:00", - "2023-03-19 20:00:00", - "2023-03-19 22:00:00", - "2023-03-20 00:00:00", - "2023-03-20 02:00:00", - "2023-03-20 04:00:00", - "2023-03-20 06:00:00", - "2023-03-20 08:00:00", - "2023-03-20 10:00:00", - "2023-03-20 12:00:00", - "2023-03-20 14:00:00", - "2023-03-20 16:00:00", - "2023-03-20 18:00:00", - "2023-03-20 20:00:00", - "2023-03-20 22:00:00", - "2023-03-21 00:00:00", - "2023-03-21 02:00:00", - "2023-03-21 04:00:00", - "2023-03-21 06:00:00", - "2023-03-21 08:00:00", - "2023-03-21 10:00:00", - "2023-03-21 12:00:00", - "2023-03-21 14:00:00", - "2023-03-21 16:00:00", - "2023-03-21 18:00:00", - "2023-03-21 20:00:00", - "2023-03-21 22:00:00", - "2023-03-22 00:00:00", - "2023-03-22 02:00:00", - "2023-03-22 04:00:00", - "2023-03-22 06:00:00", - "2023-03-22 08:00:00", - "2023-03-22 10:00:00", - "2023-03-22 12:00:00", - "2023-03-22 14:00:00", - "2023-03-22 16:00:00", - "2023-03-22 18:00:00", - "2023-03-22 20:00:00", - "2023-03-22 22:00:00", - "2023-03-23 00:00:00", - "2023-03-23 02:00:00", - "2023-03-23 04:00:00", - "2023-03-23 06:00:00", - "2023-03-23 08:00:00", - "2023-03-23 10:00:00", - "2023-03-23 12:00:00", - "2023-03-23 14:00:00", - "2023-03-23 16:00:00", - "2023-03-23 18:00:00", - "2023-03-23 20:00:00", - "2023-03-23 22:00:00", - "2023-03-24 00:00:00", - "2023-03-24 02:00:00", - "2023-03-24 04:00:00", - "2023-03-24 06:00:00", - "2023-03-24 08:00:00", - "2023-03-24 10:00:00", - "2023-03-24 12:00:00", - "2023-03-24 14:00:00", - "2023-03-24 16:00:00", - "2023-03-24 18:00:00", - "2023-03-24 20:00:00", - "2023-03-24 22:00:00", - "2023-03-25 00:00:00", - "2023-03-25 02:00:00", - "2023-03-25 04:00:00", - "2023-03-25 06:00:00", - "2023-03-25 08:00:00", - "2023-03-25 10:00:00", - "2023-03-25 12:00:00", - "2023-03-25 14:00:00", - "2023-03-25 16:00:00", - "2023-03-25 18:00:00", - "2023-03-25 20:00:00", - "2023-03-25 22:00:00", - "2023-03-26 00:00:00", - "2023-03-26 02:00:00", - "2023-03-26 04:00:00", - "2023-03-26 06:00:00", - "2023-03-26 08:00:00", - "2023-03-26 10:00:00", - "2023-03-26 12:00:00", - "2023-03-26 14:00:00", - "2023-03-26 16:00:00", - "2023-03-26 18:00:00", - "2023-03-26 20:00:00", - "2023-03-26 22:00:00", - "2023-03-27 00:00:00", - "2023-03-27 02:00:00", - "2023-03-27 04:00:00", - "2023-03-27 06:00:00", - "2023-03-27 08:00:00", - "2023-03-27 10:00:00", - "2023-03-27 12:00:00", - "2023-03-27 14:00:00", - "2023-03-27 16:00:00", - "2023-03-27 18:00:00", - "2023-03-27 20:00:00", - "2023-03-27 22:00:00", - "2023-03-28 00:00:00", - "2023-03-28 02:00:00", - "2023-03-28 04:00:00", - "2023-03-28 06:00:00", - "2023-03-28 08:00:00", - "2023-03-28 10:00:00", - "2023-03-28 12:00:00", - "2023-03-28 14:00:00", - "2023-03-28 16:00:00", - "2023-03-28 18:00:00", - "2023-03-28 20:00:00", - "2023-03-28 22:00:00", - "2023-03-29 00:00:00", - "2023-03-29 02:00:00", - "2023-03-29 04:00:00", - "2023-03-29 06:00:00", - "2023-03-29 08:00:00", - "2023-03-29 10:00:00", - "2023-03-29 12:00:00", - "2023-03-29 14:00:00", - "2023-03-29 16:00:00", - "2023-03-29 18:00:00", - "2023-03-29 20:00:00", - "2023-03-29 22:00:00", - "2023-03-30 00:00:00", - "2023-03-30 02:00:00", - "2023-03-30 04:00:00", - "2023-03-30 06:00:00", - "2023-03-30 08:00:00", - "2023-03-30 10:00:00", - "2023-03-30 12:00:00", - "2023-03-30 14:00:00", - "2023-03-30 16:00:00", - "2023-03-30 18:00:00", - "2023-03-30 20:00:00", - "2023-03-30 22:00:00", - "2023-03-31 00:00:00", - "2023-03-31 02:00:00", - "2023-03-31 04:00:00", - "2023-03-31 06:00:00", - "2023-03-31 08:00:00", - "2023-03-31 10:00:00", - "2023-03-31 12:00:00", - "2023-03-31 14:00:00", - "2023-03-31 16:00:00", - "2023-03-31 18:00:00", - "2023-03-31 20:00:00", - "2023-03-31 22:00:00", - "2023-04-01 00:00:00", - "2023-04-01 02:00:00", - "2023-04-01 04:00:00", - "2023-04-01 06:00:00", - "2023-04-01 08:00:00", - "2023-04-01 10:00:00", - "2023-04-01 12:00:00", - "2023-04-01 14:00:00", - "2023-04-01 16:00:00", - "2023-04-01 18:00:00", - "2023-04-01 20:00:00", - "2023-04-01 22:00:00", - "2023-04-02 00:00:00", - "2023-04-02 02:00:00", - "2023-04-02 04:00:00", - "2023-04-02 06:00:00", - "2023-04-02 08:00:00", - "2023-04-02 10:00:00", - "2023-04-02 12:00:00", - "2023-04-02 14:00:00", - "2023-04-02 16:00:00", - "2023-04-02 18:00:00", - "2023-04-02 20:00:00", - "2023-04-02 22:00:00", - "2023-04-03 00:00:00", - "2023-04-03 02:00:00", - "2023-04-03 04:00:00", - "2023-04-03 06:00:00", - "2023-04-03 08:00:00", - "2023-04-03 10:00:00", - "2023-04-03 12:00:00", - "2023-04-03 14:00:00", - "2023-04-03 16:00:00", - "2023-04-03 18:00:00", - "2023-04-03 20:00:00", - "2023-04-03 22:00:00", - "2023-04-04 00:00:00", - "2023-04-04 02:00:00", - "2023-04-04 04:00:00", - "2023-04-04 06:00:00", - "2023-04-04 08:00:00", - "2023-04-04 10:00:00", - "2023-04-04 12:00:00", - "2023-04-04 14:00:00", - "2023-04-04 16:00:00", - "2023-04-04 18:00:00", - "2023-04-04 20:00:00", - "2023-04-04 22:00:00", - "2023-04-05 00:00:00", - "2023-04-05 02:00:00", - "2023-04-05 04:00:00", - "2023-04-05 06:00:00", - "2023-04-05 08:00:00", - "2023-04-05 10:00:00", - "2023-04-05 12:00:00", - "2023-04-05 14:00:00", - "2023-04-05 16:00:00", - "2023-04-05 18:00:00", - "2023-04-05 20:00:00", - "2023-04-05 22:00:00", - "2023-04-06 00:00:00", - "2023-04-06 02:00:00", - "2023-04-06 04:00:00", - "2023-04-06 06:00:00", - "2023-04-06 08:00:00", - "2023-04-06 10:00:00", - "2023-04-06 12:00:00", - "2023-04-06 14:00:00", - "2023-04-06 16:00:00", - "2023-04-06 18:00:00", - "2023-04-06 20:00:00", - "2023-04-06 22:00:00", - "2023-04-07 00:00:00", - "2023-04-07 02:00:00", - "2023-04-07 04:00:00", - "2023-04-07 06:00:00", - "2023-04-07 08:00:00", - "2023-04-07 10:00:00", - "2023-04-07 12:00:00", - "2023-04-07 14:00:00", - "2023-04-07 16:00:00", - "2023-04-07 18:00:00", - "2023-04-07 20:00:00", - "2023-04-07 22:00:00", - "2023-04-08 00:00:00", - "2023-04-08 02:00:00", - "2023-04-08 04:00:00", - "2023-04-08 06:00:00", - "2023-04-08 08:00:00", - "2023-04-08 10:00:00", - "2023-04-08 12:00:00", - "2023-04-08 14:00:00", - "2023-04-08 16:00:00", - "2023-04-08 18:00:00", - "2023-04-08 20:00:00", - "2023-04-08 22:00:00", - "2023-04-09 00:00:00", - "2023-04-09 02:00:00", - "2023-04-09 04:00:00", - "2023-04-09 06:00:00", - "2023-04-09 08:00:00", - "2023-04-09 10:00:00", - "2023-04-09 12:00:00", - "2023-04-09 14:00:00", - "2023-04-09 16:00:00", - "2023-04-09 18:00:00", - "2023-04-09 20:00:00", - "2023-04-09 22:00:00", - "2023-04-10 00:00:00", - "2023-04-10 02:00:00", - "2023-04-10 04:00:00", - "2023-04-10 06:00:00", - "2023-04-10 08:00:00", - "2023-04-10 10:00:00", - "2023-04-10 12:00:00", - "2023-04-10 14:00:00", - "2023-04-10 16:00:00", - "2023-04-10 18:00:00", - "2023-04-10 20:00:00", - "2023-04-10 22:00:00", - "2023-04-11 00:00:00", - "2023-04-11 02:00:00", - "2023-04-11 04:00:00", - "2023-04-11 06:00:00", - "2023-04-11 08:00:00", - "2023-04-11 10:00:00", - "2023-04-11 12:00:00", - "2023-04-11 14:00:00", - "2023-04-11 16:00:00", - "2023-04-11 18:00:00", - "2023-04-11 20:00:00", - "2023-04-11 22:00:00", - "2023-04-12 00:00:00", - "2023-04-12 02:00:00", - "2023-04-12 04:00:00", - "2023-04-12 06:00:00", - "2023-04-12 08:00:00", - "2023-04-12 10:00:00", - "2023-04-12 12:00:00", - "2023-04-12 14:00:00", - "2023-04-12 16:00:00", - "2023-04-12 18:00:00", - "2023-04-12 20:00:00", - "2023-04-12 22:00:00", - "2023-04-13 00:00:00", - "2023-04-13 02:00:00", - "2023-04-13 04:00:00", - "2023-04-13 06:00:00", - "2023-04-13 08:00:00", - "2023-04-13 10:00:00", - "2023-04-13 12:00:00", - "2023-04-13 14:00:00", - "2023-04-13 16:00:00", - "2023-04-13 18:00:00", - "2023-04-13 20:00:00", - "2023-04-13 22:00:00", - "2023-04-14 00:00:00", - "2023-04-14 02:00:00", - "2023-04-14 04:00:00", - "2023-04-14 06:00:00", - "2023-04-14 08:00:00", - "2023-04-14 10:00:00", - "2023-04-14 12:00:00", - "2023-04-14 14:00:00", - "2023-04-14 16:00:00", - "2023-04-14 18:00:00", - "2023-04-14 20:00:00", - "2023-04-14 22:00:00", - "2023-04-15 00:00:00", - "2023-04-15 02:00:00", - "2023-04-15 04:00:00", - "2023-04-15 06:00:00", - "2023-04-15 08:00:00", - "2023-04-15 10:00:00", - "2023-04-15 12:00:00", - "2023-04-15 14:00:00", - "2023-04-15 16:00:00", - "2023-04-15 18:00:00", - "2023-04-15 20:00:00", - "2023-04-15 22:00:00", - "2023-04-16 00:00:00", - "2023-04-16 04:00:00", - "2023-04-16 06:00:00", - "2023-04-16 08:00:00", - "2023-04-16 10:00:00", - "2023-04-16 12:00:00", - "2023-04-16 14:00:00", - "2023-04-16 16:00:00", - "2023-04-16 18:00:00", - "2023-04-16 20:00:00", - "2023-04-16 22:00:00", - "2023-04-17 00:00:00", - "2023-04-17 02:00:00", - "2023-04-17 04:00:00", - "2023-04-17 06:00:00", - "2023-04-17 08:00:00", - "2023-04-17 10:00:00", - "2023-04-17 12:00:00", - "2023-04-17 14:00:00", - "2023-04-17 16:00:00", - "2023-04-17 18:00:00", - "2023-04-17 20:00:00", - "2023-04-17 22:00:00", - "2023-04-18 00:00:00", - "2023-04-18 02:00:00", - "2023-04-18 04:00:00", - "2023-04-18 06:00:00", - "2023-04-18 08:00:00", - "2023-04-18 10:00:00", - "2023-04-18 12:00:00", - "2023-04-18 14:00:00", - "2023-04-18 16:00:00", - "2023-04-18 18:00:00", - "2023-04-18 20:00:00", - "2023-04-18 22:00:00", - "2023-04-19 00:00:00", - "2023-04-19 02:00:00", - "2023-04-19 04:00:00", - "2023-04-19 06:00:00", - "2023-04-19 08:00:00", - "2023-04-19 10:00:00", - "2023-04-19 12:00:00", - "2023-04-19 14:00:00", - "2023-04-19 16:00:00", - "2023-04-19 18:00:00", - "2023-04-19 20:00:00", - "2023-04-19 22:00:00", - "2023-04-20 00:00:00", - "2023-04-20 02:00:00", - "2023-04-20 04:00:00", - "2023-04-20 06:00:00", - "2023-04-20 08:00:00", - "2023-04-20 10:00:00", - "2023-04-20 12:00:00", - "2023-04-20 14:00:00", - "2023-04-20 16:00:00", - "2023-04-20 18:00:00", - "2023-04-20 20:00:00", - "2023-04-20 22:00:00", - "2023-04-21 00:00:00", - "2023-04-21 02:00:00", - "2023-04-21 04:00:00", - "2023-04-21 06:00:00", - "2023-04-21 08:00:00", - "2023-04-21 10:00:00", - "2023-04-21 12:00:00", - "2023-04-21 14:00:00", - "2023-04-21 16:00:00", - "2023-04-21 18:00:00", - "2023-04-21 20:00:00", - "2023-04-21 22:00:00", - "2023-04-22 00:00:00", - "2023-04-22 02:00:00", - "2023-04-22 04:00:00", - "2023-04-22 06:00:00", - "2023-04-22 08:00:00", - "2023-04-22 10:00:00", - "2023-04-22 12:00:00", - "2023-04-22 14:00:00", - "2023-04-22 16:00:00", - "2023-04-22 18:00:00", - "2023-04-22 20:00:00", - "2023-04-22 22:00:00", - "2023-04-23 00:00:00", - "2023-04-23 02:00:00", - "2023-04-23 04:00:00", - "2023-04-23 06:00:00", - "2023-04-23 08:00:00", - "2023-04-23 10:00:00", - "2023-04-23 12:00:00", - "2023-04-23 14:00:00", - "2023-04-23 16:00:00", - "2023-04-23 18:00:00", - "2023-04-23 20:00:00", - "2023-04-23 22:00:00", - "2023-04-24 00:00:00", - "2023-04-24 02:00:00", - "2023-04-24 04:00:00", - "2023-04-24 06:00:00", - "2023-04-24 08:00:00", - "2023-04-24 10:00:00", - "2023-04-24 12:00:00", - "2023-04-24 14:00:00", - "2023-04-24 16:00:00", - "2023-04-24 18:00:00", - "2023-04-24 20:00:00", - "2023-04-24 22:00:00", - "2023-04-25 00:00:00", - "2023-04-25 02:00:00", - "2023-04-25 04:00:00", - "2023-04-25 06:00:00", - "2023-04-25 08:00:00", - "2023-04-25 10:00:00", - "2023-04-25 12:00:00", - "2023-04-25 14:00:00", - "2023-04-25 16:00:00", - "2023-04-25 18:00:00", - "2023-04-25 20:00:00", - "2023-04-25 22:00:00", - "2023-04-26 00:00:00", - "2023-04-26 02:00:00", - "2023-04-26 04:00:00", - "2023-04-26 06:00:00", - "2023-04-26 08:00:00", - "2023-04-26 10:00:00", - "2023-04-26 12:00:00", - "2023-04-26 14:00:00", - "2023-04-26 16:00:00", - "2023-04-26 18:00:00", - "2023-04-26 20:00:00", - "2023-04-26 22:00:00", - "2023-04-27 00:00:00", - "2023-04-27 02:00:00", - "2023-04-27 04:00:00", - "2023-04-27 06:00:00", - "2023-04-27 08:00:00", - "2023-04-27 10:00:00", - "2023-04-27 12:00:00", - "2023-04-27 14:00:00", - "2023-04-27 16:00:00", - "2023-04-27 18:00:00", - "2023-04-27 20:00:00", - "2023-04-27 22:00:00", - "2023-04-28 00:00:00", - "2023-04-28 02:00:00", - "2023-04-28 04:00:00", - "2023-04-28 06:00:00", - "2023-04-28 08:00:00", - "2023-04-28 10:00:00", - "2023-04-28 12:00:00", - "2023-04-28 14:00:00", - "2023-04-28 16:00:00", - "2023-04-28 18:00:00", - "2023-04-28 20:00:00", - "2023-04-28 22:00:00", - "2023-04-29 00:00:00", - "2023-04-29 02:00:00", - "2023-04-29 04:00:00", - "2023-04-29 06:00:00", - "2023-04-29 08:00:00", - "2023-04-29 10:00:00", - "2023-04-29 12:00:00", - "2023-04-29 14:00:00", - "2023-04-29 16:00:00", - "2023-04-29 18:00:00", - "2023-04-29 20:00:00", - "2023-04-29 22:00:00", - "2023-04-30 00:00:00", - "2023-04-30 02:00:00", - "2023-04-30 04:00:00", - "2023-04-30 06:00:00", - "2023-04-30 08:00:00", - "2023-04-30 10:00:00", - "2023-04-30 12:00:00", - "2023-04-30 14:00:00", - "2023-04-30 16:00:00", - "2023-04-30 18:00:00", - "2023-04-30 20:00:00", - "2023-04-30 22:00:00", - "2023-05-01 00:00:00", - "2023-05-01 02:00:00", - "2023-05-01 04:00:00", - "2023-05-01 06:00:00", - "2023-05-01 08:00:00", - "2023-05-01 10:00:00", - "2023-05-01 12:00:00", - "2023-05-01 14:00:00", - "2023-05-01 16:00:00", - "2023-05-01 18:00:00", - "2023-05-01 20:00:00", - "2023-05-01 22:00:00", - "2023-05-02 00:00:00", - "2023-05-02 02:00:00", - "2023-05-02 04:00:00", - "2023-05-02 06:00:00", - "2023-05-02 08:00:00", - "2023-05-02 10:00:00", - "2023-05-02 12:00:00", - "2023-05-02 14:00:00", - "2023-05-02 16:00:00", - "2023-05-02 18:00:00", - "2023-05-02 20:00:00", - "2023-05-02 22:00:00", - "2023-05-03 00:00:00", - "2023-05-03 02:00:00", - "2023-05-03 04:00:00", - "2023-05-03 06:00:00", - "2023-05-03 08:00:00", - "2023-05-03 10:00:00", - "2023-05-03 12:00:00", - "2023-05-03 14:00:00", - "2023-05-03 16:00:00", - "2023-05-03 18:00:00", - "2023-05-03 20:00:00", - "2023-05-03 22:00:00", - "2023-05-04 00:00:00", - "2023-05-04 02:00:00", - "2023-05-04 04:00:00", - "2023-05-04 06:00:00", - "2023-05-04 08:00:00", - "2023-05-04 10:00:00", - "2023-05-04 12:00:00", - "2023-05-04 14:00:00", - "2023-05-04 16:00:00", - "2023-05-04 18:00:00", - "2023-05-04 20:00:00", - "2023-05-04 22:00:00", - "2023-05-05 00:00:00", - "2023-05-05 02:00:00", - "2023-05-05 04:00:00", - "2023-05-05 06:00:00", - "2023-05-05 08:00:00", - "2023-05-05 10:00:00", - "2023-05-05 12:00:00", - "2023-05-05 14:00:00", - "2023-05-05 16:00:00", - "2023-05-05 18:00:00", - "2023-05-05 20:00:00", - "2023-05-05 22:00:00", - "2023-05-06 00:00:00", - "2023-05-06 02:00:00", - "2023-05-06 04:00:00", - "2023-05-06 06:00:00", - "2023-05-06 08:00:00", - "2023-05-06 10:00:00", - "2023-05-06 12:00:00", - "2023-05-06 14:00:00", - "2023-05-06 16:00:00", - "2023-05-06 18:00:00", - "2023-05-06 20:00:00", - "2023-05-06 22:00:00", - "2023-05-07 00:00:00", - "2023-05-07 02:00:00", - "2023-05-07 04:00:00", - "2023-05-07 06:00:00", - "2023-05-07 08:00:00", - "2023-05-07 10:00:00", - "2023-05-07 12:00:00", - "2023-05-07 14:00:00", - "2023-05-07 16:00:00", - "2023-05-07 18:00:00", - "2023-05-07 20:00:00", - "2023-05-07 22:00:00", - "2023-05-08 00:00:00", - "2023-05-08 02:00:00", - "2023-05-08 04:00:00", - "2023-05-08 06:00:00", - "2023-05-08 08:00:00", - "2023-05-08 10:00:00", - "2023-05-08 12:00:00", - "2023-05-08 14:00:00", - "2023-05-08 16:00:00", - "2023-05-08 18:00:00", - "2023-05-08 20:00:00", - "2023-05-08 22:00:00", - "2023-05-09 00:00:00", - "2023-05-09 02:00:00", - "2023-05-09 04:00:00", - "2023-05-09 06:00:00", - "2023-05-09 08:00:00", - "2023-05-09 10:00:00", - "2023-05-09 12:00:00", - "2023-05-09 14:00:00", - "2023-05-09 16:00:00", - "2023-05-09 18:00:00", - "2023-05-09 20:00:00", - "2023-05-09 22:00:00", - "2023-05-10 00:00:00", - "2023-05-10 02:00:00", - "2023-05-10 04:00:00", - "2023-05-10 06:00:00", - "2023-05-10 08:00:00", - "2023-05-10 10:00:00", - "2023-05-10 12:00:00", - "2023-05-10 14:00:00", - "2023-05-10 16:00:00", - "2023-05-10 18:00:00", - "2023-05-10 20:00:00", - "2023-05-10 22:00:00", - "2023-05-11 00:00:00", - "2023-05-11 02:00:00", - "2023-05-11 04:00:00", - "2023-05-11 06:00:00", - "2023-05-11 08:00:00", - "2023-05-11 10:00:00", - "2023-05-11 12:00:00", - "2023-05-11 14:00:00", - "2023-05-11 16:00:00", - "2023-05-11 18:00:00", - "2023-05-11 20:00:00", - "2023-05-11 22:00:00", - "2023-05-12 00:00:00", - "2023-05-12 02:00:00", - "2023-05-12 04:00:00", - "2023-05-12 06:00:00", - "2023-05-12 08:00:00", - "2023-05-12 10:00:00", - "2023-05-12 12:00:00", - "2023-05-12 14:00:00", - "2023-05-12 16:00:00", - "2023-05-12 18:00:00", - "2023-05-12 20:00:00", - "2023-05-12 22:00:00", - "2023-05-13 00:00:00", - "2023-05-13 02:00:00", - "2023-05-13 04:00:00", - "2023-05-13 06:00:00", - "2023-05-13 08:00:00", - "2023-05-13 10:00:00", - "2023-05-13 12:00:00", - "2023-05-13 14:00:00", - "2023-05-13 16:00:00", - "2023-05-13 18:00:00", - "2023-05-13 20:00:00", - "2023-05-13 22:00:00", - "2023-05-14 00:00:00", - "2023-05-14 02:00:00", - "2023-05-14 04:00:00", - "2023-05-14 06:00:00", - "2023-05-14 08:00:00", - "2023-05-14 10:00:00", - "2023-05-14 12:00:00", - "2023-05-14 14:00:00", - "2023-05-14 16:00:00", - "2023-05-14 18:00:00", - "2023-05-14 20:00:00", - "2023-05-14 22:00:00", - "2023-05-15 00:00:00", - "2023-05-15 02:00:00", - "2023-05-15 04:00:00", - "2023-05-15 06:00:00", - "2023-05-15 08:00:00", - "2023-05-15 10:00:00", - "2023-05-15 12:00:00", - "2023-05-15 14:00:00", - "2023-05-15 16:00:00", - "2023-05-15 18:00:00", - "2023-05-15 20:00:00", - "2023-05-15 22:00:00", - "2023-05-16 00:00:00", - "2023-05-16 02:00:00", - "2023-05-16 04:00:00", - "2023-05-16 06:00:00", - "2023-05-16 08:00:00", - "2023-05-16 10:00:00", - "2023-05-16 12:00:00", - "2023-05-16 14:00:00", - "2023-05-16 16:00:00", - "2023-05-16 18:00:00", - "2023-05-16 20:00:00", - "2023-05-16 22:00:00", - "2023-05-17 00:00:00", - "2023-05-17 02:00:00", - "2023-05-17 04:00:00", - "2023-05-17 06:00:00", - "2023-05-17 08:00:00", - "2023-05-17 10:00:00", - "2023-05-17 12:00:00", - "2023-05-17 14:00:00", - "2023-05-17 16:00:00", - "2023-05-17 18:00:00", - "2023-05-17 20:00:00", - "2023-05-17 22:00:00", - "2023-05-18 00:00:00", - "2023-05-18 02:00:00", - "2023-05-18 04:00:00", - "2023-05-18 06:00:00", - "2023-05-18 08:00:00", - "2023-05-18 10:00:00", - "2023-05-18 12:00:00", - "2023-05-18 14:00:00", - "2023-05-18 16:00:00", - "2023-05-18 18:00:00", - "2023-05-18 20:00:00", - "2023-05-18 22:00:00", - "2023-05-19 00:00:00", - "2023-05-19 02:00:00", - "2023-05-19 04:00:00", - "2023-05-19 06:00:00", - "2023-05-19 08:00:00", - "2023-05-19 10:00:00", - "2023-05-19 12:00:00", - "2023-05-19 14:00:00", - "2023-05-19 16:00:00", - "2023-05-19 18:00:00", - "2023-05-19 20:00:00", - "2023-05-19 22:00:00", - "2023-05-20 00:00:00", - "2023-05-20 02:00:00", - "2023-05-20 04:00:00", - "2023-05-20 06:00:00", - "2023-05-20 08:00:00", - "2023-05-20 10:00:00", - "2023-05-20 12:00:00", - "2023-05-20 14:00:00", - "2023-05-20 16:00:00", - "2023-05-20 18:00:00", - "2023-05-20 20:00:00", - "2023-05-20 22:00:00", - "2023-05-21 00:00:00", - "2023-05-21 02:00:00", - "2023-05-21 04:00:00", - "2023-05-21 06:00:00", - "2023-05-21 08:00:00", - "2023-05-21 10:00:00", - "2023-05-21 12:00:00", - "2023-05-21 14:00:00", - "2023-05-21 16:00:00", - "2023-05-21 18:00:00", - "2023-05-21 20:00:00", - "2023-05-21 22:00:00", - "2023-05-22 00:00:00", - "2023-05-22 02:00:00", - "2023-05-22 04:00:00", - "2023-05-22 06:00:00", - "2023-05-22 08:00:00", - "2023-05-22 10:00:00", - "2023-05-22 12:00:00", - "2023-05-22 14:00:00", - "2023-05-22 16:00:00", - "2023-05-22 18:00:00", - "2023-05-22 20:00:00", - "2023-05-22 22:00:00", - "2023-05-23 00:00:00", - "2023-05-23 02:00:00", - "2023-05-23 04:00:00", - "2023-05-23 06:00:00", - "2023-05-23 08:00:00", - "2023-05-23 10:00:00", - "2023-05-23 12:00:00", - "2023-05-23 14:00:00", - "2023-05-23 16:00:00", - "2023-05-23 18:00:00", - "2023-05-23 20:00:00", - "2023-05-23 22:00:00", - "2023-05-24 00:00:00", - "2023-05-24 02:00:00", - "2023-05-24 04:00:00", - "2023-05-24 06:00:00", - "2023-05-24 08:00:00", - "2023-05-24 10:00:00", - "2023-05-24 12:00:00", - "2023-05-24 14:00:00", - "2023-05-24 16:00:00", - "2023-05-24 18:00:00", - "2023-05-24 20:00:00", - "2023-05-24 22:00:00", - "2023-05-25 00:00:00", - "2023-05-25 02:00:00", - "2023-05-25 04:00:00", - "2023-05-25 06:00:00", - "2023-05-25 08:00:00", - "2023-05-25 10:00:00", - "2023-05-25 12:00:00", - "2023-05-25 14:00:00", - "2023-05-25 16:00:00", - "2023-05-25 18:00:00", - "2023-05-25 20:00:00", - "2023-05-25 22:00:00", - "2023-05-26 00:00:00", - "2023-05-26 02:00:00", - "2023-05-26 04:00:00", - "2023-05-26 06:00:00", - "2023-05-26 08:00:00", - "2023-05-26 10:00:00", - "2023-05-26 12:00:00", - "2023-05-26 14:00:00", - "2023-05-26 16:00:00", - "2023-05-26 18:00:00", - "2023-05-26 20:00:00", - "2023-05-26 22:00:00", - "2023-05-27 00:00:00", - "2023-05-27 02:00:00", - "2023-05-27 04:00:00", - "2023-05-27 06:00:00", - "2023-05-27 08:00:00", - "2023-05-27 10:00:00", - "2023-05-27 12:00:00", - "2023-05-27 14:00:00", - "2023-05-27 16:00:00", - "2023-05-27 18:00:00", - "2023-05-27 20:00:00", - "2023-05-27 22:00:00", - "2023-05-28 00:00:00", - "2023-05-28 02:00:00", - "2023-05-28 04:00:00", - "2023-05-28 06:00:00", - "2023-05-28 08:00:00", - "2023-05-28 10:00:00", - "2023-05-28 12:00:00", - "2023-05-28 14:00:00", - "2023-05-28 16:00:00", - "2023-05-28 18:00:00", - "2023-05-28 20:00:00", - "2023-05-28 22:00:00", - "2023-05-29 00:00:00", - "2023-05-29 02:00:00", - "2023-05-29 04:00:00", - "2023-05-29 06:00:00", - "2023-05-29 08:00:00", - "2023-05-29 10:00:00", - "2023-05-29 12:00:00", - "2023-05-29 14:00:00", - "2023-05-29 16:00:00", - "2023-05-29 18:00:00", - "2023-05-29 20:00:00", - "2023-05-29 22:00:00", - "2023-05-30 00:00:00", - "2023-05-30 02:00:00", - "2023-05-30 04:00:00", - "2023-05-30 06:00:00", - "2023-05-30 08:00:00", - "2023-05-30 10:00:00", - "2023-05-30 12:00:00", - "2023-05-30 14:00:00", - "2023-05-30 16:00:00", - "2023-05-30 18:00:00", - "2023-05-30 20:00:00", - "2023-05-30 22:00:00", - "2023-05-31 00:00:00", - "2023-05-31 02:00:00", - "2023-05-31 04:00:00", - "2023-05-31 06:00:00", - "2023-05-31 08:00:00", - "2023-05-31 10:00:00", - "2023-05-31 12:00:00", - "2023-05-31 14:00:00", - "2023-05-31 16:00:00", - "2023-05-31 18:00:00", - "2023-05-31 20:00:00", - "2023-05-31 22:00:00", - "2023-06-01 00:00:00" - ], - "y": [ - 15603.0, - 15748.0, - 15831.0, - 15832.0, - 15801.0, - 15795.0, - 15801.0, - 15906.0, - 15839.0, - 15898.0, - 15877.0, - 15906.0, - 15868.0, - 15858.0, - 15839.0, - 15818.0, - 15901.0, - 15916.0, - 15846.0, - 15870.0, - 15867.0, - 15774.0, - 15837.0, - 15860.0, - 15820.0, - 15832.0, - 15811.0, - 15805.0, - 15828.0, - 15857.0, - 15793.0, - 15692.0, - 15678.0, - 15734.0, - 15843.0, - 15861.0, - 15825.0, - 15859.0, - 15857.0, - 15850.0, - 15866.0, - 15873.0, - 15883.0, - 15863.0, - 15865.0, - 15869.0, - 15839.0, - 15773.0, - 15832.0, - 15847.0, - 15814.0, - 15854.0, - 15854.0, - 15818.0, - 15809.0, - 15845.0, - 15848.0, - 15847.0, - 15834.0, - 15822.0, - 15826.0, - 15823.0, - 15843.0, - 15834.0, - 15854.0, - 15820.0, - 15830.0, - 15819.0, - 15815.0, - 15805.0, - 15842.0, - 15831.0, - 15879.0, - 15912.0, - 15848.0, - 15827.0, - 15837.0, - 15866.0, - 15842.0, - 15811.0, - 15837.0, - 15836.0, - 15815.0, - 15897.0, - 15820.0, - 15824.0, - 15802.0, - 15821.0, - 15807.0, - 15786.0, - 15823.0, - 15755.0, - 15675.0, - 15678.0, - 15700.0, - 15707.0, - 15673.0, - 15658.0, - 15592.0, - 15637.0, - 15631.0, - 15650.0, - 15619.0, - 15585.0, - 15647.0, - 15622.0, - 15550.0, - 15569.0, - 15544.0, - 15560.0, - 15606.0, - 15579.0, - 15578.0, - 15599.0, - 15578.0, - 15561.0, - 15532.0, - 15562.0, - 15565.0, - 15580.0, - 15585.0, - 15581.0, - 15529.0, - 15473.0, - 15501.0, - 15452.0, - 15438.0, - 15523.0, - 15415.0, - 15454.0, - 15497.0, - 15518.0, - 15479.0, - 15451.0, - 15452.0, - 15476.0, - 15445.0, - 15461.0, - 15510.0, - 15504.0, - 15489.0, - 15470.0, - 15444.0, - 15439.0, - 15437.0, - 15433.0, - 15425.0, - 15426.0, - 15439.0, - 15465.0, - 15462.0, - 15442.0, - 15466.0, - 15504.0, - 15516.0, - 15533.0, - 15483.0, - 15569.0, - 15549.0, - 15661.0, - 15653.0, - 15660.0, - 15629.0, - 15692.0, - 15666.0, - 15682.0, - 15682.0, - 15605.0, - 15639.0, - 15621.0, - 15637.0, - 15710.0, - 15859.0, - 15874.0, - 15833.0, - 15790.0, - 15763.0, - 15729.0, - 15770.0, - 15784.0, - 15803.0, - 15929.0, - 15921.0, - 15897.0, - 15869.0, - 15869.0, - 15856.0, - 15889.0, - 15936.0, - 15872.0, - 15849.0, - 15896.0, - 15838.0, - 15866.0, - 15863.0, - 15847.0, - 15797.0, - 15848.0, - 15895.0, - 16001.0, - 15970.0, - 16001.0, - 15992.0, - 16001.0, - 15975.0, - 15991.0, - 15988.0, - 15966.0, - 15953.0, - 15944.0, - 15887.0, - 15871.0, - 15823.0, - 15888.0, - 15891.0, - 15924.0, - 15926.0, - 15922.0, - 15910.0, - 15929.0, - 15888.0, - 15887.0, - 15901.0, - 15926.0, - 15919.0, - 15910.0, - 15904.0, - 15911.0, - 15912.0, - 15921.0, - 15923.0, - 15904.0, - 15911.0, - 15910.0, - 15883.0, - 15962.0, - 15883.0, - 15879.0, - 15918.0, - 16069.0, - 16114.0, - 16126.0, - 16091.0, - 16114.0, - 16156.0, - 16148.0, - 16070.0, - 16073.0, - 16150.0, - 16101.0, - 16016.0, - 16017.0, - 16027.0, - 16035.0, - 16043.0, - 16010.0, - 16050.0, - 16065.0, - 16074.0, - 16144.0, - 16135.0, - 16222.0, - 16287.0, - 16235.0, - 16282.0, - 16240.0, - 16171.0, - 16238.0, - 16245.0, - 16230.0, - 16210.0, - 16124.0, - 16171.0, - 16299.0, - 16307.0, - 16635.0, - 16923.0, - 16922.0, - 16795.0, - 16836.0, - 16875.0, - 16914.0, - 16896.0, - 16736.0, - 17360.0, - 17430.0, - 17350.0, - 17374.0, - 17297.0, - 17389.0, - 17342.0, - 17347.0, - 17462.0, - 17515.0, - 17482.0, - 17816.0, - 17800.0, - 17880.0, - 18281.0, - 18384.0, - 19320.0, - 19309.0, - 19211.0, - 19301.0, - 18935.0, - 19122.0, - 19328.0, - 19164.0, - 19137.0, - 19170.0, - 19260.0, - 19347.0, - 19157.0, - 19081.0, - 19129.0, - 19092.0, - 19136.0, - 19102.0, - 19068.0, - 19281.0, - 19301.0, - 19243.0, - 19301.0, - 19277.0, - 19496.0, - 19412.0, - 19533.0, - 19500.0, - 19244.0, - 19237.0, - 19251.0, - 19381.0, - 19524.0, - 19700.0, - 19510.0, - 19566.0, - 19466.0, - 19529.0, - 19541.0, - 19536.0, - 19595.0, - 19579.0, - 19606.0, - 19628.0, - 19694.0, - 19763.0, - 19751.0, - 19610.0, - 19675.0, - 19759.0, - 19783.0, - 19696.0, - 19548.0, - 19616.0, - 19722.0, - 19436.0, - 19338.0, - 19347.0, - 19225.0, - 19133.0, - 19171.0, - 19185.0, - 19278.0, - 19224.0, - 19205.0, - 19131.0, - 19233.0, - 19337.0, - 19265.0, - 19488.0, - 19338.0, - 19447.0, - 19499.0, - 19465.0, - 19367.0, - 19339.0, - 19365.0, - 19340.0, - 19487.0, - 19540.0, - 19678.0, - 19797.0, - 20531.0, - 20858.0, - 20779.0, - 20757.0, - 20790.0, - 20848.0, - 21235.0, - 21113.0, - 21118.0, - 21186.0, - 21426.0, - 21469.0, - 21376.0, - 20990.0, - 21030.0, - 20981.0, - 21108.0, - 21128.0, - 21088.0, - 20985.0, - 21121.0, - 20997.0, - 21131.0, - 20936.0, - 20794.0, - 20912.0, - 20868.0, - 20793.0, - 20876.0, - 20813.0, - 20884.0, - 21062.0, - 21005.0, - 21046.0, - 21230.0, - 21003.0, - 21145.0, - 21078.0, - 21242.0, - 21234.0, - 21262.0, - 21160.0, - 21053.0, - 21089.0, - 21054.0, - 21068.0, - 21032.0, - 21147.0, - 21054.0, - 20799.0, - 20665.0, - 20764.0, - 20817.0, - 20897.0, - 20777.0, - 20784.0, - 20771.0, - 20693.0, - 20707.0, - 20854.0, - 21615.0, - 21126.0, - 21217.0, - 21176.0, - 21226.0, - 21045.0, - 21070.0, - 21108.0, - 21216.0, - 21151.0, - 21136.0, - 21173.0, - 21196.0, - 21112.0, - 20772.0, - 20957.0, - 21162.0, - 21206.0, - 21083.0, - 21110.0, - 21078.0, - 21188.0, - 21365.0, - 21389.0, - 21248.0, - 21257.0, - 21241.0, - 21304.0, - 21243.0, - 21182.0, - 21160.0, - 21168.0, - 21169.0, - 21194.0, - 21176.0, - 21224.0, - 21172.0, - 21212.0, - 21380.0, - 21337.0, - 21400.0, - 21369.0, - 21421.0, - 21540.0, - 21665.0, - 21628.0, - 21693.0, - 21988.0, - 21913.0, - 21840.0, - 21766.0, - 21810.0, - 21835.0, - 21785.0, - 21251.0, - 21191.0, - 21176.0, - 21341.0, - 21296.0, - 21000.0, - 20977.0, - 21067.0, - 21050.0, - 21062.0, - 21000.0, - 21206.0, - 21138.0, - 21112.0, - 21308.0, - 21303.0, - 21318.0, - 21317.0, - 21132.0, - 21311.0, - 21282.0, - 21293.0, - 21270.0, - 21213.0, - 21141.0, - 21172.0, - 21180.0, - 21058.0, - 21092.0, - 21321.0, - 21550.0, - 21550.0, - 21752.0, - 21632.0, - 21611.0, - 21625.0, - 21646.0, - 21646.0, - 21714.0, - 21812.0, - 21909.0, - 21827.0, - 21507.0, - 21532.0, - 21638.0, - 21600.0, - 21578.0, - 21532.0, - 21445.0, - 21554.0, - 21553.0, - 21730.0, - 21731.0, - 21562.0, - 21671.0, - 21687.0, - 21659.0, - 21600.0, - 21601.0, - 21622.0, - 21634.0, - 21655.0, - 21758.0, - 21695.0, - 21729.0, - 21711.0, - 21671.0, - 21626.0, - 21616.0, - 21623.0, - 21656.0, - 21680.0, - 21661.0, - 21654.0, - 21491.0, - 21398.0, - 21169.0, - 21216.0, - 21248.0, - 21269.0, - 21301.0, - 21229.0, - 21099.0, - 21250.0, - 21227.0, - 21239.0, - 21209.0, - 21459.0, - 21499.0, - 21453.0, - 21369.0, - 21216.0, - 21248.0, - 21298.0, - 21341.0, - 21354.0, - 21481.0, - 21462.0, - 21495.0, - 21400.0, - 21616.0, - 21546.0, - 21604.0, - 21661.0, - 21673.0, - 21670.0, - 21630.0, - 21576.0, - 21552.0, - 21570.0, - 21525.0, - 21332.0, - 21435.0, - 21339.0, - 21425.0, - 21445.0, - 21378.0, - 20992.0, - 21073.0, - 21137.0, - 21130.0, - 21081.0, - 21059.0, - 21012.0, - 20974.0, - 20522.0, - 20348.0, - 20303.0, - 20406.0, - 20349.0, - 20329.0, - 20400.0, - 20400.0, - 20318.0, - 20371.0, - 20296.0, - 20346.0, - 20392.0, - 20180.0, - 20275.0, - 20275.0, - 20320.0, - 20318.0, - 20333.0, - 20334.0, - 20334.0, - 20392.0, - 20350.0, - 20322.0, - 20312.0, - 20468.0, - 20505.0, - 20446.0, - 20409.0, - 20437.0, - 20428.0, - 20551.0, - 20522.0, - 20475.0, - 20573.0, - 20623.0, - 20651.0, - 20385.0, - 20421.0, - 20342.0, - 20470.0, - 20471.0, - 20493.0, - 20240.0, - 20231.0, - 20200.0, - 20139.0, - 20040.0, - 20160.0, - 20148.0, - 20302.0, - 20223.0, - 20225.0, - 20251.0, - 20304.0, - 20281.0, - 20294.0, - 20199.0, - 20595.0, - 20547.0, - 20704.0, - 20709.0, - 20690.0, - 20597.0, - 20601.0, - 20668.0, - 20629.0, - 20679.0, - 20950.0, - 21220.0, - 21370.0, - 21370.0, - 21799.0, - 22596.0, - 22714.0, - 22969.0, - 23072.0, - 23014.0, - 22929.0, - 22992.0, - 22947.0, - 22852.0, - 23500.0, - 23268.0, - 23264.0, - 22982.0, - 22043.0, - 22395.0, - 22378.0, - 22286.0, - 22224.0, - 22334.0, - 22393.0, - 22360.0, - 22653.0, - 22754.0, - 22936.0, - 22861.0, - 22970.0, - 23068.0, - 22987.0, - 23049.0, - 22925.0, - 22971.0, - 22913.0, - 22971.0, - 23065.0, - 23082.0, - 23022.0, - 23018.0, - 23029.0, - 23108.0, - 23049.0, - 23127.0, - 22982.0, - 22981.0, - 23083.0, - 23089.0, - 23265.0, - 22780.0, - 22951.0, - 22961.0, - 22724.0, - 22715.0, - 22867.0, - 22921.0, - 22905.0, - 23265.0, - 23277.0, - 23242.0, - 23315.0, - 23221.0, - 23243.0, - 23186.0, - 23250.0, - 23299.0, - 23369.0, - 23363.0, - 23436.0, - 23261.0, - 23066.0, - 23075.0, - 23000.0, - 23156.0, - 23126.0, - 22748.0, - 22974.0, - 22715.0, - 22721.0, - 22634.0, - 22582.0, - 22674.0, - 22746.0, - 22669.0, - 22338.0, - 22352.0, - 22463.0, - 22440.0, - 22816.0, - 22787.0, - 23028.0, - 22950.0, - 22936.0, - 22929.0, - 22467.0, - 22644.0, - 22590.0, - 22537.0, - 22650.0, - 22556.0, - 22602.0, - 22652.0, - 22628.0, - 22573.0, - 22530.0, - 22543.0, - 22586.0, - 22526.0, - 22176.0, - 21906.0, - 21965.0, - 21936.0, - 22009.0, - 22016.0, - 21904.0, - 21881.0, - 21943.0, - 21899.0, - 21801.0, - 21812.0, - 21857.0, - 21875.0, - 21808.0, - 21822.0, - 22067.0, - 21989.0, - 22103.0, - 22000.0, - 22046.0, - 22178.0, - 22130.0, - 22082.0, - 22146.0, - 22085.0, - 22362.0, - 22389.0, - 22335.0, - 22333.0, - 22334.0, - 22194.0, - 22209.0, - 22143.0, - 22198.0, - 22416.0, - 22247.0, - 22034.0, - 21967.0, - 22035.0, - 22142.0, - 22153.0, - 22167.0, - 22104.0, - 21937.0, - 21953.0, - 22068.0, - 22030.0, - 22180.0, - 22181.0, - 21991.0, - 21895.0, - 21885.0, - 21986.0, - 22143.0, - 22357.0, - 22269.0, - 22330.0, - 22249.0, - 22205.0, - 22210.0, - 22237.0, - 21929.0, - 22089.0, - 22163.0, - 22089.0, - 22090.0, - 22036.0, - 21980.0, - 22042.0, - 22030.0, - 22028.0, - 22010.0, - 21992.0, - 22135.0, - 22102.0, - 22115.0, - 20961.0, - 21075.0, - 21086.0, - 21121.0, - 21104.0, - 21076.0, - 21116.0, - 21098.0, - 21104.0, - 21035.0, - 20917.0, - 21031.0, - 21026.0, - 21039.0, - 21017.0, - 21034.0, - 21007.0, - 21043.0, - 21047.0, - 20998.0, - 20979.0, - 20931.0, - 20864.0, - 21008.0, - 21256.0, - 21051.0, - 21092.0, - 21071.0, - 21052.0, - 21066.0, - 21148.0, - 21124.0, - 21094.0, - 21093.0, - 21171.0, - 21126.0, - 21073.0, - 21046.0, - 21009.0, - 21030.0, - 21079.0, - 21010.0, - 21056.0, - 21139.0, - 21049.0, - 20971.0, - 20972.0, - 20962.0, - 21051.0, - 21000.0, - 21022.0, - 20977.0, - 20996.0, - 20965.0, - 20996.0, - 21065.0, - 21112.0, - 20968.0, - 20913.0, - 21042.0, - 21019.0, - 21024.0, - 20849.0, - 20812.0, - 20887.0, - 20948.0, - 20859.0, - 20983.0, - 20895.0, - 20853.0, - 20845.0, - 20587.0, - 20593.0, - 20641.0, - 20577.0, - 20520.0, - 20475.0, - 20482.0, - 20518.0, - 20446.0, - 20269.0, - 19683.0, - 19145.0, - 19257.0, - 18984.0, - 18946.0, - 18903.0, - 18843.0, - 18779.0, - 18673.0, - 19026.0, - 18760.0, - 18697.0, - 18844.0, - 18902.0, - 18989.0, - 19567.0, - 19342.0, - 19277.0, - 18786.0, - 18949.0, - 19034.0, - 18942.0, - 18888.0, - 19017.0, - 19050.0, - 19155.0, - 19219.0, - 19217.0, - 19181.0, - 19200.0, - 19195.0, - 19216.0, - 19237.0, - 19209.0, - 19257.0, - 19638.0, - 19577.0, - 19974.0, - 20665.0, - 20993.0, - 20775.0, - 20880.0, - 20891.0, - 20562.0, - 20616.0, - 21000.0, - 22323.0, - 22407.0, - 22505.0, - 22192.0, - 22338.0, - 22600.0, - 22678.0, - 22547.0, - 22488.0, - 22613.0, - 23072.0, - 23972.0, - 24160.0, - 23810.0, - 23275.0, - 22855.0, - 23000.0, - 23137.0, - 22987.0, - 23006.0, - 23194.0, - 22994.0, - 23422.0, - 23514.0, - 23244.0, - 23036.0, - 23052.0, - 23163.0, - 22973.0, - 22982.0, - 22945.0, - 23000.0, - 23212.0, - 23257.0, - 23469.0, - 23433.0, - 23475.0, - 23324.0, - 23590.0, - 23523.0, - 23605.0, - 24196.0, - 24237.0, - 24200.0, - 24511.0, - 24721.0, - 25360.0, - 24911.0, - 24863.0, - 24884.0, - 25192.0, - 25426.0, - 25656.0, - 25591.0, - 25620.0, - 25879.0, - 25699.0, - 25708.0, - 25765.0, - 25732.0, - 25518.0, - 25591.0, - 25678.0, - 25568.0, - 25245.0, - 25419.0, - 25539.0, - 25411.0, - 25280.0, - 25350.0, - 25492.0, - 25506.0, - 25854.0, - 26222.0, - 26549.0, - 26259.0, - 26264.0, - 25987.0, - 25661.0, - 25882.0, - 26519.0, - 26484.0, - 26441.0, - 26025.0, - 25908.0, - 25882.0, - 25967.0, - 26166.0, - 25952.0, - 26091.0, - 25968.0, - 26048.0, - 25756.0, - 26098.0, - 26103.0, - 26100.0, - 26100.0, - 26055.0, - 26200.0, - 26083.0, - 26173.0, - 26158.0, - 26200.0, - 26279.0, - 26160.0, - 26119.0, - 26132.0, - 26340.0, - 26535.0, - 26451.0, - 24551.0, - 25010.0, - 25158.0, - 25005.0, - 25207.0, - 25443.0, - 25420.0, - 25450.0, - 25447.0, - 25207.0, - 26296.0, - 26075.0, - 26216.0, - 26042.0, - 26145.0, - 26121.0, - 26102.0, - 26157.0, - 26130.0, - 26149.0, - 25769.0, - 25967.0, - 26064.0, - 25672.0, - 25921.0, - 25508.0, - 25596.0, - 25615.0, - 25686.0, - 25588.0, - 25622.0, - 25596.0, - 25581.0, - 25679.0, - 25719.0, - 25604.0, - 25323.0, - 25562.0, - 25570.0, - 25708.0, - 25618.0, - 25631.0, - 25619.0, - 25740.0, - 25940.0, - 26165.0, - 25902.0, - 25922.0, - 25908.0, - 25911.0, - 25968.0, - 25864.0, - 25901.0, - 25725.0, - 25873.0, - 25956.0, - 25886.0, - 25697.0, - 24973.0, - 25000.0, - 25003.0, - 25180.0, - 25140.0, - 25062.0, - 24966.0, - 24972.0, - 24920.0, - 24880.0, - 25012.0, - 24832.0, - 24911.0, - 24798.0, - 25268.0, - 25063.0, - 25187.0, - 25230.0, - 25256.0, - 25449.0, - 25892.0, - 26044.0, - 26073.0, - 26175.0, - 26240.0, - 26018.0, - 26198.0, - 26089.0, - 26155.0, - 26185.0, - 26256.0, - 26394.0, - 26493.0, - 26341.0, - 26343.0, - 26122.0, - 25999.0, - 25579.0, - 25686.0, - 25775.0, - 25760.0, - 25833.0, - 25871.0, - 25757.0, - 25510.0, - 25596.0, - 25714.0, - 26017.0, - 26194.0, - 26113.0, - 26237.0, - 26269.0, - 26233.0, - 26388.0, - 26302.0, - 26327.0, - 26207.0, - 26231.0, - 26200.0, - 26205.0, - 26212.0, - 26192.0, - 26161.0, - 26296.0, - 26234.0, - 26211.0, - 26245.0, - 26259.0, - 26195.0, - 26216.0, - 26156.0, - 26103.0, - 25979.0, - 26061.0, - 25837.0, - 25868.0, - 26082.0, - 25668.0, - 25756.0, - 25701.0, - 25796.0, - 26128.0, - 25984.0, - 25986.0, - 25810.0, - 25745.0, - 25793.0, - 25448.0, - 25507.0, - 25520.0, - 25656.0, - 25644.0, - 25662.0, - 25828.0, - 25942.0, - 25753.0, - 25622.0, - 25790.0, - 25746.0, - 25782.0, - 25711.0, - 26066.0, - 26029.0, - 26100.0, - 26041.0, - 26044.0, - 26108.0, - 25987.0, - 25718.0, - 25755.0, - 25926.0, - 25856.0, - 25857.0, - 25780.0, - 25855.0, - 25766.0, - 25615.0, - 25593.0, - 25630.0, - 25624.0, - 25717.0, - 25674.0, - 25714.0, - 25678.0, - 25715.0, - 25735.0, - 25722.0, - 25672.0, - 25535.0, - 25571.0, - 25534.0, - 25673.0, - 25662.0, - 25632.0, - 25622.0, - 25600.0, - 25629.0, - 25638.0, - 25731.0, - 25818.0, - 25812.0, - 25741.0, - 25754.0, - 25742.0, - 25722.0, - 25689.0, - 25634.0, - 25658.0, - 25685.0, - 25821.0, - 25787.0, - 25750.0, - 25650.0, - 25651.0, - 25677.0, - 25665.0, - 25650.0, - 25644.0, - 25811.0, - 26090.0, - 25952.0, - 25970.0, - 25961.0, - 25989.0, - 25967.0, - 25980.0, - 26034.0, - 26011.0, - 26236.0, - 26837.0, - 26853.0, - 26889.0, - 27264.0, - 27668.0, - 27647.0, - 27387.0, - 27615.0, - 27545.0, - 27537.0, - 27687.0, - 27700.0, - 27678.0, - 27620.0, - 27692.0, - 27652.0, - 27649.0, - 27414.0, - 27382.0, - 27452.0, - 27490.0, - 27503.0, - 27405.0, - 27317.0, - 27316.0, - 27102.0, - 27258.0, - 27179.0, - 27357.0, - 27379.0, - 27419.0, - 27368.0, - 27413.0, - 27410.0, - 27334.0, - 27567.0, - 27580.0, - 27513.0, - 27451.0, - 27541.0, - 27842.0, - 27730.0, - 27847.0, - 27799.0, - 27854.0, - 27905.0, - 27890.0, - 27554.0, - 27586.0, - 27617.0, - 27730.0, - 27706.0, - 27639.0, - 27639.0, - 27696.0, - 27717.0, - 27748.0, - 27665.0, - 27640.0, - 27610.0, - 27570.0, - 27621.0, - 27576.0, - 27577.0, - 27544.0, - 27611.0, - 27652.0, - 27583.0, - 27548.0, - 27541.0, - 27616.0, - 27585.0, - 27594.0, - 27659.0, - 27625.0, - 27336.0, - 27340.0, - 27321.0, - 27134.0, - 27243.0, - 26973.0, - 26894.0, - 26923.0, - 27003.0, - 27039.0, - 27020.0, - 26978.0, - 26955.0, - 26998.0, - 27100.0, - 27212.0, - 27258.0, - 27680.0, - 27715.0, - 27550.0, - 27389.0, - 27583.0, - 27689.0, - 27691.0, - 27575.0, - 27552.0, - 27635.0, - 27457.0, - 26751.0, - 26844.0, - 26801.0, - 26747.0, - 26790.0, - 26703.0, - 26641.0, - 26343.0, - 26409.0, - 26364.0, - 26383.0, - 26358.0, - 26342.0, - 26113.0, - 26275.0, - 25972.0, - 25955.0, - 25644.0, - 25750.0, - 25757.0, - 25734.0, - 25831.0, - 25681.0, - 25693.0, - 25630.0, - 25552.0, - 25657.0, - 25557.0, - 25403.0, - 24834.0, - 24833.0, - 24793.0, - 24831.0, - 24842.0, - 24951.0, - 24860.0, - 24829.0, - 24880.0, - 24895.0, - 25104.0, - 25341.0, - 25250.0, - 25330.0, - 25503.0, - 25182.0, - 25148.0, - 25238.0, - 25399.0, - 25320.0, - 25280.0, - 25267.0, - 25084.0, - 25131.0, - 25092.0, - 25063.0, - 25136.0, - 25267.0, - 25326.0, - 25247.0, - 24979.0, - 24839.0, - 25034.0, - 25067.0, - 24732.0, - 24800.0, - 24810.0, - 24824.0, - 24876.0, - 24792.0, - 24727.0, - 24794.0, - 24712.0, - 24763.0, - 24869.0, - 24859.0, - 24937.0, - 25203.0, - 25197.0, - 25782.0, - 25795.0, - 25844.0, - 25800.0, - 25868.0, - 25763.0, - 26157.0, - 26188.0, - 26910.0, - 26986.0, - 26920.0, - 25365.0, - 25999.0, - 25735.0, - 26034.0, - 26162.0, - 26324.0, - 26180.0, - 26239.0, - 26238.0, - 26273.0, - 26463.0, - 26576.0, - 26888.0, - 26879.0, - 26735.0, - 26777.0, - 26686.0, - 26781.0, - 26792.0, - 26587.0, - 26678.0, - 26404.0, - 26426.0, - 26547.0, - 26617.0, - 26620.0, - 26588.0, - 26576.0, - 26646.0, - 26659.0, - 26657.0, - 26586.0, - 26619.0, - 26701.0, - 26595.0, - 26486.0, - 26561.0, - 26546.0, - 26529.0, - 26425.0, - 26481.0, - 26568.0, - 26579.0, - 26556.0, - 26514.0, - 26580.0, - 26920.0, - 26887.0, - 26667.0, - 26703.0, - 26526.0, - 25946.0, - 25963.0, - 25913.0, - 26039.0, - 25944.0, - 25948.0, - 25865.0, - 25717.0, - 25801.0, - 25397.0, - 25548.0, - 25603.0, - 25503.0, - 25586.0, - 25521.0, - 25538.0, - 25559.0, - 25630.0, - 25583.0, - 25980.0, - 26003.0, - 26062.0, - 26053.0, - 26032.0, - 25883.0, - 25869.0, - 25860.0, - 25981.0, - 25971.0, - 25874.0, - 25732.0, - 25645.0, - 25847.0, - 25656.0, - 26152.0, - 26182.0, - 26226.0, - 26246.0, - 26373.0, - 26262.0, - 26316.0, - 26338.0, - 26102.0, - 26257.0, - 26161.0, - 26241.0, - 26207.0, - 26161.0, - 26548.0, - 26434.0, - 26453.0, - 26344.0, - 26396.0, - 26462.0, - 26565.0, - 26642.0, - 26763.0, - 26817.0, - 26762.0, - 26783.0, - 26837.0, - 26658.0, - 26631.0, - 26673.0, - 26626.0, - 26609.0, - 26316.0, - 26007.0, - 26198.0, - 26253.0, - 26220.0, - 26256.0, - 26236.0, - 26211.0, - 26233.0, - 26308.0, - 26205.0, - 26229.0, - 26275.0, - 26322.0, - 26291.0, - 26322.0, - 26137.0, - 25868.0, - 25639.0, - 25606.0, - 25659.0, - 25382.0, - 25359.0, - 25358.0, - 25264.0, - 25340.0, - 25106.0, - 24889.0, - 25039.0, - 25197.0, - 25263.0, - 25174.0, - 25067.0, - 25221.0, - 25121.0, - 25272.0, - 25223.0, - 25045.0, - 25213.0, - 25280.0, - 25189.0, - 25217.0, - 25265.0, - 25238.0, - 25255.0, - 25137.0, - 25159.0, - 25275.0, - 25666.0, - 25683.0, - 25093.0, - 25231.0, - 25082.0, - 25156.0, - 25094.0, - 25024.0, - 25106.0, - 25130.0, - 25106.0, - 25080.0, - 24931.0, - 24904.0, - 24687.0, - 24600.0, - 24729.0, - 24748.0, - 24606.0, - 24327.0, - 24084.0, - 24127.0, - 24202.0, - 24214.0, - 24309.0, - 24254.0, - 24234.0, - 24397.0, - 24691.0, - 24695.0, - 24743.0, - 24725.0, - 24662.0, - 24689.0, - 24759.0, - 24731.0, - 24741.0, - 24746.0, - 24745.0, - 24782.0, - 24797.0, - 24708.0, - 24614.0, - 24784.0, - 24746.0, - 24748.0, - 24767.0, - 24714.0, - 24817.0, - 24959.0, - 24847.0, - 24777.0, - 24790.0, - 24825.0, - 25053.0, - 25082.0, - 25170.0, - 25208.0, - 25223.0, - 25152.0, - 25175.0, - 25200.0, - 25284.0, - 25209.0, - 25173.0, - 24984.0, - 24835.0, - 24945.0, - 24893.0, - 25048.0, - 24865.0, - 24854.0, - 24810.0, - 24886.0, - 24923.0, - 24802.0, - 24857.0, - 24888.0, - 24991.0, - 24912.0, - 24851.0, - 24781.0, - 24817.0, - 24600.0, - 24623.0, - 24753.0, - 25009.0, - 25263.0, - 25228.0, - 25260.0, - 25182.0, - 25225.0, - 25119.0, - 25317.0, - 25332.0, - 25322.0, - 25285.0, - 25115.0, - 24611.0, - 24827.0, - 24977.0, - 24893.0, - 24909.0, - 24925.0, - 24942.0, - 24921.0, - 24855.0, - 24865.0, - 24903.0, - 24864.0, - 24875.0, - 24849.0, - 24910.0, - 24878.0, - 24870.0, - 24869.0, - 24876.0, - 24864.0, - 24899.0, - 24893.0, - 24889.0, - 24954.0, - 25120.0, - 25044.0, - 25024.0, - 25089.0, - 25114.0, - 25151.0, - 25077.0, - 25022.0, - 25028.0, - 24836.0, - 24873.0, - 24889.0, - 24858.0, - 24901.0, - 24763.0, - 24711.0, - 24589.0, - 24635.0, - 24762.0, - 24845.0, - 24821.0, - 24798.0, - 24977.0, - 24880.0, - 24831.0, - 24820.0, - 24884.0, - 24842.0, - 24970.0, - 25304.0, - 25360.0, - 25258.0, - 25282.0, - 25359.0, - 25253.0, - 25352.0, - 25229.0, - 25251.0, - 25225.0, - 25274.0, - 25203.0, - 24840.0, - 24837.0, - 24742.0, - 24829.0, - 24819.0, - 24579.0, - 24458.0, - 24438.0, - 24407.0, - 24537.0, - 24500.0, - 24311.0, - 24435.0, - 24525.0, - 24399.0, - 24500.0, - 24518.0, - 24589.0, - 24441.0, - 24566.0, - 24675.0, - 24689.0, - 24695.0, - 24615.0, - 24631.0, - 24545.0, - 24674.0, - 24652.0, - 24619.0, - 24734.0, - 24965.0, - 24909.0, - 24954.0, - 24911.0, - 24910.0, - 24894.0, - 24957.0, - 24909.0, - 24926.0, - 24910.0, - 24908.0, - 24844.0, - 24893.0, - 24945.0, - 24933.0, - 24953.0, - 25047.0, - 25238.0, - 25322.0, - 25309.0, - 25346.0, - 25292.0, - 25300.0, - 25301.0, - 25402.0, - 25470.0, - 25627.0, - 25948.0, - 26162.0, - 26245.0, - 26038.0, - 26053.0, - 26006.0, - 26053.0, - 26056.0, - 26035.0, - 25780.0, - 25856.0, - 25800.0, - 25837.0, - 25915.0, - 25893.0, - 25973.0, - 25960.0, - 25951.0, - 26001.0, - 26092.0, - 25921.0, - 25811.0, - 25866.0, - 25956.0, - 25835.0, - 25820.0, - 25904.0, - 25850.0, - 25332.0, - 25452.0, - 25431.0, - 25389.0, - 25380.0, - 25278.0, - 25388.0, - 25292.0, - 25350.0, - 25469.0, - 25358.0 - ], "type": "scatter", - "xaxis": "x2", - "yaxis": "y2" - }, - { - "line": { - "color": "blue", - "width": 1 - }, - "mode": "lines", - "name": "Close side ways", - "x": [ - "2022-06-10 02:00:00", - "2022-06-10 04:00:00", - "2022-06-10 06:00:00", - "2022-06-10 08:00:00", - "2022-06-10 10:00:00", - "2022-06-10 12:00:00", - "2022-06-10 14:00:00", - "2022-06-10 16:00:00", - "2022-06-10 18:00:00", - "2022-06-10 20:00:00", - "2022-06-10 22:00:00", - "2022-06-11 00:00:00", - "2022-06-11 02:00:00", - "2022-06-11 04:00:00", - "2022-06-11 06:00:00", - "2022-06-11 08:00:00", - "2022-06-11 10:00:00", - "2022-06-11 12:00:00", - "2022-06-11 14:00:00", - "2022-06-11 16:00:00", - "2022-06-11 18:00:00", - "2022-06-11 20:00:00", - "2022-06-11 22:00:00", - "2022-06-12 00:00:00", - "2022-06-12 02:00:00", - "2022-06-12 04:00:00", - "2022-06-12 06:00:00", - "2022-06-12 08:00:00", - "2022-06-12 10:00:00", - "2022-06-12 12:00:00", - "2022-06-12 14:00:00", - "2022-06-12 16:00:00", - "2022-06-12 18:00:00", - "2022-06-12 20:00:00", - "2022-06-12 22:00:00", - "2022-06-13 00:00:00", - "2022-06-13 02:00:00", - "2022-06-13 04:00:00", - "2022-06-13 06:00:00", - "2022-06-13 08:00:00", - "2022-06-13 10:00:00", - "2022-06-13 12:00:00", - "2022-06-13 14:00:00", - "2022-06-13 16:00:00", - "2022-06-13 18:00:00", - "2022-06-13 20:00:00", - "2022-06-13 22:00:00", - "2022-06-14 00:00:00", - "2022-06-14 02:00:00", - "2022-06-14 04:00:00", - "2022-06-14 06:00:00", - "2022-06-14 08:00:00", - "2022-06-14 10:00:00", - "2022-06-14 12:00:00", - "2022-06-14 14:00:00", - "2022-06-14 16:00:00", - "2022-06-14 18:00:00", - "2022-06-14 20:00:00", - "2022-06-14 22:00:00", - "2022-06-15 00:00:00", - "2022-06-15 02:00:00", - "2022-06-15 04:00:00", - "2022-06-15 06:00:00", - "2022-06-15 08:00:00", - "2022-06-15 10:00:00", - "2022-06-15 12:00:00", - "2022-06-15 14:00:00", - "2022-06-15 16:00:00", - "2022-06-15 18:00:00", - "2022-06-15 20:00:00", - "2022-06-15 22:00:00", - "2022-06-16 00:00:00", - "2022-06-16 02:00:00", - "2022-06-16 04:00:00", - "2022-06-16 06:00:00", - "2022-06-16 08:00:00", - "2022-06-16 10:00:00", - "2022-06-16 12:00:00", - "2022-06-16 14:00:00", - "2022-06-16 16:00:00", - "2022-06-16 18:00:00", - "2022-06-16 20:00:00", - "2022-06-16 22:00:00", - "2022-06-17 00:00:00", - "2022-06-17 02:00:00", - "2022-06-17 04:00:00", - "2022-06-17 06:00:00", - "2022-06-17 08:00:00", - "2022-06-17 10:00:00", - "2022-06-17 12:00:00", - "2022-06-17 14:00:00", - "2022-06-17 16:00:00", - "2022-06-17 18:00:00", - "2022-06-17 20:00:00", - "2022-06-17 22:00:00", - "2022-06-18 00:00:00", - "2022-06-18 02:00:00", - "2022-06-18 04:00:00", - "2022-06-18 06:00:00", - "2022-06-18 08:00:00", - "2022-06-18 10:00:00", - "2022-06-18 12:00:00", - "2022-06-18 14:00:00", - "2022-06-18 16:00:00", - "2022-06-18 18:00:00", - "2022-06-18 20:00:00", - "2022-06-18 22:00:00", - "2022-06-19 00:00:00", - "2022-06-19 02:00:00", - "2022-06-19 04:00:00", - "2022-06-19 06:00:00", - "2022-06-19 08:00:00", - "2022-06-19 10:00:00", - "2022-06-19 12:00:00", - "2022-06-19 14:00:00", - "2022-06-19 16:00:00", - "2022-06-19 18:00:00", - "2022-06-19 20:00:00", - "2022-06-19 22:00:00", - "2022-06-20 00:00:00", - "2022-06-20 02:00:00", - "2022-06-20 04:00:00", - "2022-06-20 06:00:00", - "2022-06-20 08:00:00", - "2022-06-20 10:00:00", - "2022-06-20 12:00:00", - "2022-06-20 14:00:00", - "2022-06-20 16:00:00", - "2022-06-20 18:00:00", - "2022-06-20 20:00:00", - "2022-06-20 22:00:00", - "2022-06-21 00:00:00", - "2022-06-21 02:00:00", - "2022-06-21 04:00:00", - "2022-06-21 06:00:00", - "2022-06-21 08:00:00", - "2022-06-21 10:00:00", - "2022-06-21 12:00:00", - "2022-06-21 14:00:00", - "2022-06-21 16:00:00", - "2022-06-21 18:00:00", - "2022-06-21 20:00:00", - "2022-06-21 22:00:00", - "2022-06-22 00:00:00", - "2022-06-22 02:00:00", - "2022-06-22 04:00:00", - "2022-06-22 06:00:00", - "2022-06-22 08:00:00", - "2022-06-22 10:00:00", - "2022-06-22 12:00:00", - "2022-06-22 14:00:00", - "2022-06-22 16:00:00", - "2022-06-22 18:00:00", - "2022-06-22 20:00:00", - "2022-06-22 22:00:00", - "2022-06-23 00:00:00", - "2022-06-23 02:00:00", - "2022-06-23 04:00:00", - "2022-06-23 06:00:00", - "2022-06-23 08:00:00", - "2022-06-23 10:00:00", - "2022-06-23 12:00:00", - "2022-06-23 14:00:00", - "2022-06-23 16:00:00", - "2022-06-23 18:00:00", - "2022-06-23 20:00:00", - "2022-06-23 22:00:00", - "2022-06-24 00:00:00", - "2022-06-24 02:00:00", - "2022-06-24 04:00:00", - "2022-06-24 06:00:00", - "2022-06-24 08:00:00", - "2022-06-24 10:00:00", - "2022-06-24 12:00:00", - "2022-06-24 14:00:00", - "2022-06-24 16:00:00", - "2022-06-24 18:00:00", - "2022-06-24 20:00:00", - "2022-06-24 22:00:00", - "2022-06-25 00:00:00", - "2022-06-25 02:00:00", - "2022-06-25 04:00:00", - "2022-06-25 06:00:00", - "2022-06-25 08:00:00", - "2022-06-25 10:00:00", - "2022-06-25 12:00:00", - "2022-06-25 14:00:00", - "2022-06-25 16:00:00", - "2022-06-25 18:00:00", - "2022-06-25 20:00:00", - "2022-06-25 22:00:00", - "2022-06-26 00:00:00", - "2022-06-26 02:00:00", - "2022-06-26 04:00:00", - "2022-06-26 06:00:00", - "2022-06-26 08:00:00", - "2022-06-26 10:00:00", - "2022-06-26 12:00:00", - "2022-06-26 14:00:00", - "2022-06-26 16:00:00", - "2022-06-26 18:00:00", - "2022-06-26 20:00:00", - "2022-06-26 22:00:00", - "2022-06-27 00:00:00", - "2022-06-27 02:00:00", - "2022-06-27 04:00:00", - "2022-06-27 06:00:00", - "2022-06-27 08:00:00", - "2022-06-27 10:00:00", - "2022-06-27 12:00:00", - "2022-06-27 14:00:00", - "2022-06-27 16:00:00", - "2022-06-27 18:00:00", - "2022-06-27 20:00:00", - "2022-06-27 22:00:00", - "2022-06-28 00:00:00", - "2022-06-28 02:00:00", - "2022-06-28 04:00:00", - "2022-06-28 06:00:00", - "2022-06-28 08:00:00", - "2022-06-28 10:00:00", - "2022-06-28 12:00:00", - "2022-06-28 14:00:00", - "2022-06-28 16:00:00", - "2022-06-28 18:00:00", - "2022-06-28 20:00:00", - "2022-06-28 22:00:00", - "2022-06-29 00:00:00", - "2022-06-29 02:00:00", - "2022-06-29 04:00:00", - "2022-06-29 06:00:00", - "2022-06-29 08:00:00", - "2022-06-29 10:00:00", - "2022-06-29 12:00:00", - "2022-06-29 14:00:00", - "2022-06-29 16:00:00", - "2022-06-29 18:00:00", - "2022-06-29 20:00:00", - "2022-06-29 22:00:00", - "2022-06-30 00:00:00", - "2022-06-30 02:00:00", - "2022-06-30 04:00:00", - "2022-06-30 06:00:00", - "2022-06-30 08:00:00", - "2022-06-30 10:00:00", - "2022-06-30 12:00:00", - "2022-06-30 14:00:00", - "2022-06-30 16:00:00", - "2022-06-30 18:00:00", - "2022-06-30 20:00:00", - "2022-06-30 22:00:00", - "2022-07-01 00:00:00", - "2022-07-01 02:00:00", - "2022-07-01 04:00:00", - "2022-07-01 06:00:00", - "2022-07-01 08:00:00", - "2022-07-01 10:00:00", - "2022-07-01 12:00:00", - "2022-07-01 14:00:00", - "2022-07-01 16:00:00", - "2022-07-01 18:00:00", - "2022-07-01 20:00:00", - "2022-07-01 22:00:00", - "2022-07-02 00:00:00", - "2022-07-02 02:00:00", - "2022-07-02 04:00:00", - "2022-07-02 06:00:00", - "2022-07-02 08:00:00", - "2022-07-02 10:00:00", - "2022-07-02 12:00:00", - "2022-07-02 14:00:00", - "2022-07-02 16:00:00", - "2022-07-02 18:00:00", - "2022-07-02 20:00:00", - "2022-07-02 22:00:00", - "2022-07-03 00:00:00", - "2022-07-03 02:00:00", - "2022-07-03 04:00:00", - "2022-07-03 06:00:00", - "2022-07-03 08:00:00", - "2022-07-03 10:00:00", - "2022-07-03 12:00:00", - "2022-07-03 14:00:00", - "2022-07-03 16:00:00", - "2022-07-03 18:00:00", - "2022-07-03 20:00:00", - "2022-07-03 22:00:00", - "2022-07-04 00:00:00", - "2022-07-04 02:00:00", - "2022-07-04 04:00:00", - "2022-07-04 06:00:00", - "2022-07-04 08:00:00", - "2022-07-04 10:00:00", - "2022-07-04 12:00:00", - "2022-07-04 14:00:00", - "2022-07-04 16:00:00", - "2022-07-04 18:00:00", - "2022-07-04 20:00:00", - "2022-07-04 22:00:00", - "2022-07-05 00:00:00", - "2022-07-05 02:00:00", - "2022-07-05 04:00:00", - "2022-07-05 06:00:00", - "2022-07-05 08:00:00", - "2022-07-05 10:00:00", - "2022-07-05 12:00:00", - "2022-07-05 14:00:00", - "2022-07-05 16:00:00", - "2022-07-05 18:00:00", - "2022-07-05 20:00:00", - "2022-07-05 22:00:00", - "2022-07-06 00:00:00", - "2022-07-06 02:00:00", - "2022-07-06 04:00:00", - "2022-07-06 06:00:00", - "2022-07-06 08:00:00", - "2022-07-06 10:00:00", - "2022-07-06 12:00:00", - "2022-07-06 14:00:00", - "2022-07-06 16:00:00", - "2022-07-06 18:00:00", - "2022-07-06 20:00:00", - "2022-07-06 22:00:00", - "2022-07-07 00:00:00", - "2022-07-07 02:00:00", - "2022-07-07 04:00:00", - "2022-07-07 06:00:00", - "2022-07-07 08:00:00", - "2022-07-07 10:00:00", - "2022-07-07 12:00:00", - "2022-07-07 14:00:00", - "2022-07-07 16:00:00", - "2022-07-07 18:00:00", - "2022-07-07 20:00:00", - "2022-07-07 22:00:00", - "2022-07-08 00:00:00", - "2022-07-08 02:00:00", - "2022-07-08 04:00:00", - "2022-07-08 06:00:00", - "2022-07-08 08:00:00", - "2022-07-08 10:00:00", - "2022-07-08 12:00:00", - "2022-07-08 14:00:00", - "2022-07-08 16:00:00", - "2022-07-08 18:00:00", - "2022-07-08 20:00:00", - "2022-07-08 22:00:00", - "2022-07-09 00:00:00", - "2022-07-09 02:00:00", - "2022-07-09 04:00:00", - "2022-07-09 06:00:00", - "2022-07-09 08:00:00", - "2022-07-09 10:00:00", - "2022-07-09 12:00:00", - "2022-07-09 14:00:00", - "2022-07-09 16:00:00", - "2022-07-09 18:00:00", - "2022-07-09 20:00:00", - "2022-07-09 22:00:00", - "2022-07-10 00:00:00", - "2022-07-10 02:00:00", - "2022-07-10 04:00:00", - "2022-07-10 06:00:00", - "2022-07-10 08:00:00", - "2022-07-10 10:00:00", - "2022-07-10 12:00:00", - "2022-07-10 14:00:00", - "2022-07-10 16:00:00", - "2022-07-10 18:00:00", - "2022-07-10 20:00:00", - "2022-07-10 22:00:00", - "2022-07-11 00:00:00", - "2022-07-11 02:00:00", - "2022-07-11 04:00:00", - "2022-07-11 06:00:00", - "2022-07-11 08:00:00", - "2022-07-11 10:00:00", - "2022-07-11 12:00:00", - "2022-07-11 14:00:00", - "2022-07-11 16:00:00", - "2022-07-11 18:00:00", - "2022-07-11 20:00:00", - "2022-07-11 22:00:00", - "2022-07-12 00:00:00", - "2022-07-12 02:00:00", - "2022-07-12 04:00:00", - "2022-07-12 06:00:00", - "2022-07-12 08:00:00", - "2022-07-12 10:00:00", - "2022-07-12 12:00:00", - "2022-07-12 14:00:00", - "2022-07-12 16:00:00", - "2022-07-12 18:00:00", - "2022-07-12 20:00:00", - "2022-07-12 22:00:00", - "2022-07-13 00:00:00", - "2022-07-13 02:00:00", - "2022-07-13 04:00:00", - "2022-07-13 06:00:00", - "2022-07-13 08:00:00", - "2022-07-13 10:00:00", - "2022-07-13 12:00:00", - "2022-07-13 14:00:00", - "2022-07-13 16:00:00", - "2022-07-13 18:00:00", - "2022-07-13 20:00:00", - "2022-07-13 22:00:00", - "2022-07-14 00:00:00", - "2022-07-14 02:00:00", - "2022-07-14 04:00:00", - "2022-07-14 06:00:00", - "2022-07-14 08:00:00", - "2022-07-14 10:00:00", - "2022-07-14 12:00:00", - "2022-07-14 14:00:00", - "2022-07-14 16:00:00", - "2022-07-14 18:00:00", - "2022-07-14 20:00:00", - "2022-07-14 22:00:00", - "2022-07-15 00:00:00", - "2022-07-15 02:00:00", - "2022-07-15 04:00:00", - "2022-07-15 06:00:00", - "2022-07-15 08:00:00", - "2022-07-15 10:00:00", - "2022-07-15 12:00:00", - "2022-07-15 14:00:00", - "2022-07-15 16:00:00", - "2022-07-15 18:00:00", - "2022-07-15 20:00:00", - "2022-07-15 22:00:00", - "2022-07-16 00:00:00", - "2022-07-16 02:00:00", - "2022-07-16 04:00:00", - "2022-07-16 06:00:00", - "2022-07-16 08:00:00", - "2022-07-16 10:00:00", - "2022-07-16 12:00:00", - "2022-07-16 14:00:00", - "2022-07-16 16:00:00", - "2022-07-16 18:00:00", - "2022-07-16 20:00:00", - "2022-07-16 22:00:00", - "2022-07-17 00:00:00", - "2022-07-17 02:00:00", - "2022-07-17 04:00:00", - "2022-07-17 06:00:00", - "2022-07-17 08:00:00", - "2022-07-17 10:00:00", - "2022-07-17 12:00:00", - "2022-07-17 14:00:00", - "2022-07-17 16:00:00", - "2022-07-17 18:00:00", - "2022-07-17 20:00:00", - "2022-07-17 22:00:00", - "2022-07-18 00:00:00", - "2022-07-18 02:00:00", - "2022-07-18 04:00:00", - "2022-07-18 06:00:00", - "2022-07-18 08:00:00", - "2022-07-18 10:00:00", - "2022-07-18 12:00:00", - "2022-07-18 14:00:00", - "2022-07-18 16:00:00", - "2022-07-18 18:00:00", - "2022-07-18 20:00:00", - "2022-07-18 22:00:00", - "2022-07-19 00:00:00", - "2022-07-19 02:00:00", - "2022-07-19 04:00:00", - "2022-07-19 06:00:00", - "2022-07-19 08:00:00", - "2022-07-19 10:00:00", - "2022-07-19 12:00:00", - "2022-07-19 14:00:00", - "2022-07-19 16:00:00", - "2022-07-19 18:00:00", - "2022-07-19 20:00:00", - "2022-07-19 22:00:00", - "2022-07-20 00:00:00", - "2022-07-20 02:00:00", - "2022-07-20 04:00:00", - "2022-07-20 06:00:00", - "2022-07-20 08:00:00", - "2022-07-20 10:00:00", - "2022-07-20 12:00:00", - "2022-07-20 14:00:00", - "2022-07-20 16:00:00", - "2022-07-20 18:00:00", - "2022-07-20 20:00:00", - "2022-07-20 22:00:00", - "2022-07-21 00:00:00", - "2022-07-21 02:00:00", - "2022-07-21 04:00:00", - "2022-07-21 06:00:00", - "2022-07-21 08:00:00", - "2022-07-21 10:00:00", - "2022-07-21 12:00:00", - "2022-07-21 14:00:00", - "2022-07-21 16:00:00", - "2022-07-21 18:00:00", - "2022-07-21 20:00:00", - "2022-07-21 22:00:00", - "2022-07-22 00:00:00", - "2022-07-22 02:00:00", - "2022-07-22 04:00:00", - "2022-07-22 06:00:00", - "2022-07-22 08:00:00", - "2022-07-22 10:00:00", - "2022-07-22 12:00:00", - "2022-07-22 14:00:00", - "2022-07-22 16:00:00", - "2022-07-22 18:00:00", - "2022-07-22 20:00:00", - "2022-07-22 22:00:00", - "2022-07-23 00:00:00", - "2022-07-23 02:00:00", - "2022-07-23 04:00:00", - "2022-07-23 06:00:00", - "2022-07-23 08:00:00", - "2022-07-23 10:00:00", - "2022-07-23 12:00:00", - "2022-07-23 14:00:00", - "2022-07-23 16:00:00", - "2022-07-23 18:00:00", - "2022-07-23 20:00:00", - "2022-07-23 22:00:00", - "2022-07-24 00:00:00", - "2022-07-24 02:00:00", - "2022-07-24 04:00:00", - "2022-07-24 06:00:00", - "2022-07-24 08:00:00", - "2022-07-24 10:00:00", - "2022-07-24 12:00:00", - "2022-07-24 14:00:00", - "2022-07-24 16:00:00", - "2022-07-24 18:00:00", - "2022-07-24 20:00:00", - "2022-07-24 22:00:00", - "2022-07-25 00:00:00", - "2022-07-25 02:00:00", - "2022-07-25 04:00:00", - "2022-07-25 06:00:00", - "2022-07-25 08:00:00", - "2022-07-25 10:00:00", - "2022-07-25 12:00:00", - "2022-07-25 14:00:00", - "2022-07-25 16:00:00", - "2022-07-25 18:00:00", - "2022-07-25 20:00:00", - "2022-07-25 22:00:00", - "2022-07-26 00:00:00", - "2022-07-26 02:00:00", - "2022-07-26 04:00:00", - "2022-07-26 06:00:00", - "2022-07-26 08:00:00", - "2022-07-26 10:00:00", - "2022-07-26 12:00:00", - "2022-07-26 14:00:00", - "2022-07-26 16:00:00", - "2022-07-26 18:00:00", - "2022-07-26 20:00:00", - "2022-07-26 22:00:00", - "2022-07-27 00:00:00", - "2022-07-27 02:00:00", - "2022-07-27 04:00:00", - "2022-07-27 06:00:00", - "2022-07-27 08:00:00", - "2022-07-27 10:00:00", - "2022-07-27 12:00:00", - "2022-07-27 14:00:00", - "2022-07-27 16:00:00", - "2022-07-27 18:00:00", - "2022-07-27 20:00:00", - "2022-07-27 22:00:00", - "2022-07-28 00:00:00", - "2022-07-28 02:00:00", - "2022-07-28 04:00:00", - "2022-07-28 06:00:00", - "2022-07-28 08:00:00", - "2022-07-28 10:00:00", - "2022-07-28 12:00:00", - "2022-07-28 14:00:00", - "2022-07-28 16:00:00", - "2022-07-28 18:00:00", - "2022-07-28 20:00:00", - "2022-07-28 22:00:00", - "2022-07-29 00:00:00", - "2022-07-29 02:00:00", - "2022-07-29 04:00:00", - "2022-07-29 06:00:00", - "2022-07-29 08:00:00", - "2022-07-29 10:00:00", - "2022-07-29 12:00:00", - "2022-07-29 14:00:00", - "2022-07-29 16:00:00", - "2022-07-29 18:00:00", - "2022-07-29 20:00:00", - "2022-07-29 22:00:00", - "2022-07-30 00:00:00", - "2022-07-30 02:00:00", - "2022-07-30 04:00:00", - "2022-07-30 06:00:00", - "2022-07-30 08:00:00", - "2022-07-30 10:00:00", - "2022-07-30 12:00:00", - "2022-07-30 14:00:00", - "2022-07-30 16:00:00", - "2022-07-30 18:00:00", - "2022-07-30 20:00:00", - "2022-07-30 22:00:00", - "2022-07-31 00:00:00", - "2022-07-31 02:00:00", - "2022-07-31 04:00:00", - "2022-07-31 06:00:00", - "2022-07-31 08:00:00", - "2022-07-31 10:00:00", - "2022-07-31 12:00:00", - "2022-07-31 14:00:00", - "2022-07-31 16:00:00", - "2022-07-31 18:00:00", - "2022-07-31 20:00:00", - "2022-07-31 22:00:00", - "2022-08-01 00:00:00", - "2022-08-01 02:00:00", - "2022-08-01 04:00:00", - "2022-08-01 06:00:00", - "2022-08-01 08:00:00", - "2022-08-01 10:00:00", - "2022-08-01 12:00:00", - "2022-08-01 14:00:00", - "2022-08-01 16:00:00", - "2022-08-01 18:00:00", - "2022-08-01 20:00:00", - "2022-08-01 22:00:00", - "2022-08-02 00:00:00", - "2022-08-02 02:00:00", - "2022-08-02 04:00:00", - "2022-08-02 06:00:00", - "2022-08-02 08:00:00", - "2022-08-02 10:00:00", - "2022-08-02 12:00:00", - "2022-08-02 14:00:00", - "2022-08-02 16:00:00", - "2022-08-02 18:00:00", - "2022-08-02 20:00:00", - "2022-08-02 22:00:00", - "2022-08-03 00:00:00", - "2022-08-03 02:00:00", - "2022-08-03 04:00:00", - "2022-08-03 06:00:00", - "2022-08-03 08:00:00", - "2022-08-03 10:00:00", - "2022-08-03 12:00:00", - "2022-08-03 14:00:00", - "2022-08-03 16:00:00", - "2022-08-03 18:00:00", - "2022-08-03 20:00:00", - "2022-08-03 22:00:00", - "2022-08-04 00:00:00", - "2022-08-04 02:00:00", - "2022-08-04 04:00:00", - "2022-08-04 06:00:00", - "2022-08-04 08:00:00", - "2022-08-04 10:00:00", - "2022-08-04 12:00:00", - "2022-08-04 14:00:00", - "2022-08-04 16:00:00", - "2022-08-04 18:00:00", - "2022-08-04 20:00:00", - "2022-08-04 22:00:00", - "2022-08-05 00:00:00", - "2022-08-05 02:00:00", - "2022-08-05 04:00:00", - "2022-08-05 06:00:00", - "2022-08-05 08:00:00", - "2022-08-05 10:00:00", - "2022-08-05 12:00:00", - "2022-08-05 14:00:00", - "2022-08-05 16:00:00", - "2022-08-05 18:00:00", - "2022-08-05 20:00:00", - "2022-08-05 22:00:00", - "2022-08-06 00:00:00", - "2022-08-06 02:00:00", - "2022-08-06 04:00:00", - "2022-08-06 06:00:00", - "2022-08-06 08:00:00", - "2022-08-06 10:00:00", - "2022-08-06 12:00:00", - "2022-08-06 14:00:00", - "2022-08-06 16:00:00", - "2022-08-06 18:00:00", - "2022-08-06 20:00:00", - "2022-08-06 22:00:00", - "2022-08-07 00:00:00", - "2022-08-07 02:00:00", - "2022-08-07 04:00:00", - "2022-08-07 06:00:00", - "2022-08-07 08:00:00", - "2022-08-07 10:00:00", - "2022-08-07 12:00:00", - "2022-08-07 14:00:00", - "2022-08-07 16:00:00", - "2022-08-07 18:00:00", - "2022-08-07 20:00:00", - "2022-08-07 22:00:00", - "2022-08-08 00:00:00", - "2022-08-08 02:00:00", - "2022-08-08 04:00:00", - "2022-08-08 06:00:00", - "2022-08-08 08:00:00", - "2022-08-08 10:00:00", - "2022-08-08 12:00:00", - "2022-08-08 14:00:00", - "2022-08-08 16:00:00", - "2022-08-08 18:00:00", - "2022-08-08 20:00:00", - "2022-08-08 22:00:00", - "2022-08-09 00:00:00", - "2022-08-09 02:00:00", - "2022-08-09 04:00:00", - "2022-08-09 06:00:00", - "2022-08-09 08:00:00", - "2022-08-09 10:00:00", - "2022-08-09 12:00:00", - "2022-08-09 14:00:00", - "2022-08-09 16:00:00", - "2022-08-09 18:00:00", - "2022-08-09 20:00:00", - "2022-08-09 22:00:00", - "2022-08-10 00:00:00", - "2022-08-10 02:00:00", - "2022-08-10 04:00:00", - "2022-08-10 06:00:00", - "2022-08-10 08:00:00", - "2022-08-10 10:00:00", - "2022-08-10 12:00:00", - "2022-08-10 14:00:00", - "2022-08-10 16:00:00", - "2022-08-10 18:00:00", - "2022-08-10 20:00:00", - "2022-08-10 22:00:00", - "2022-08-11 00:00:00", - "2022-08-11 02:00:00", - "2022-08-11 04:00:00", - "2022-08-11 06:00:00", - "2022-08-11 08:00:00", - "2022-08-11 10:00:00", - "2022-08-11 12:00:00", - "2022-08-11 14:00:00", - "2022-08-11 16:00:00", - "2022-08-11 18:00:00", - "2022-08-11 20:00:00", - "2022-08-11 22:00:00", - "2022-08-12 00:00:00", - "2022-08-12 02:00:00", - "2022-08-12 04:00:00", - "2022-08-12 06:00:00", - "2022-08-12 08:00:00", - "2022-08-12 10:00:00", - "2022-08-12 12:00:00", - "2022-08-12 14:00:00", - "2022-08-12 16:00:00", - "2022-08-12 18:00:00", - "2022-08-12 20:00:00", - "2022-08-12 22:00:00", - "2022-08-13 00:00:00", - "2022-08-13 02:00:00", - "2022-08-13 04:00:00", - "2022-08-13 06:00:00", - "2022-08-13 08:00:00", - "2022-08-13 10:00:00", - "2022-08-13 12:00:00", - "2022-08-13 14:00:00", - "2022-08-13 16:00:00", - "2022-08-13 18:00:00", - "2022-08-13 20:00:00", - "2022-08-13 22:00:00", - "2022-08-14 00:00:00", - "2022-08-14 02:00:00", - "2022-08-14 04:00:00", - "2022-08-14 06:00:00", - "2022-08-14 08:00:00", - "2022-08-14 10:00:00", - "2022-08-14 12:00:00", - "2022-08-14 14:00:00", - "2022-08-14 16:00:00", - "2022-08-14 18:00:00", - "2022-08-14 20:00:00", - "2022-08-14 22:00:00", - "2022-08-15 00:00:00", - "2022-08-15 02:00:00", - "2022-08-15 04:00:00", - "2022-08-15 06:00:00", - "2022-08-15 08:00:00", - "2022-08-15 10:00:00", - "2022-08-15 12:00:00", - "2022-08-15 14:00:00", - "2022-08-15 16:00:00", - "2022-08-15 18:00:00", - "2022-08-15 20:00:00", - "2022-08-15 22:00:00", - "2022-08-16 00:00:00", - "2022-08-16 02:00:00", - "2022-08-16 04:00:00", - "2022-08-16 06:00:00", - "2022-08-16 08:00:00", - "2022-08-16 10:00:00", - "2022-08-16 12:00:00", - "2022-08-16 14:00:00", - "2022-08-16 16:00:00", - "2022-08-16 18:00:00", - "2022-08-16 20:00:00", - "2022-08-16 22:00:00", - "2022-08-17 00:00:00", - "2022-08-17 02:00:00", - "2022-08-17 04:00:00", - "2022-08-17 06:00:00", - "2022-08-17 08:00:00", - "2022-08-17 10:00:00", - "2022-08-17 12:00:00", - "2022-08-17 14:00:00", - "2022-08-17 16:00:00", - "2022-08-17 18:00:00", - "2022-08-17 20:00:00", - "2022-08-17 22:00:00", - "2022-08-18 00:00:00", - "2022-08-18 02:00:00", - "2022-08-18 04:00:00", - "2022-08-18 06:00:00", - "2022-08-18 08:00:00", - "2022-08-18 10:00:00", - "2022-08-18 12:00:00", - "2022-08-18 14:00:00", - "2022-08-18 16:00:00", - "2022-08-18 18:00:00", - "2022-08-18 20:00:00", - "2022-08-18 22:00:00", - "2022-08-19 00:00:00", - "2022-08-19 02:00:00", - "2022-08-19 04:00:00", - "2022-08-19 06:00:00", - "2022-08-19 08:00:00", - "2022-08-19 10:00:00", - "2022-08-19 12:00:00", - "2022-08-19 14:00:00", - "2022-08-19 16:00:00", - "2022-08-19 18:00:00", - "2022-08-19 20:00:00", - "2022-08-19 22:00:00", - "2022-08-20 00:00:00", - "2022-08-20 02:00:00", - "2022-08-20 04:00:00", - "2022-08-20 06:00:00", - "2022-08-20 08:00:00", - "2022-08-20 10:00:00", - "2022-08-20 12:00:00", - "2022-08-20 14:00:00", - "2022-08-20 16:00:00", - "2022-08-20 18:00:00", - "2022-08-20 20:00:00", - "2022-08-20 22:00:00", - "2022-08-21 00:00:00", - "2022-08-21 02:00:00", - "2022-08-21 04:00:00", - "2022-08-21 06:00:00", - "2022-08-21 08:00:00", - "2022-08-21 10:00:00", - "2022-08-21 12:00:00", - "2022-08-21 14:00:00", - "2022-08-21 16:00:00", - "2022-08-21 18:00:00", - "2022-08-21 20:00:00", - "2022-08-21 22:00:00", - "2022-08-22 00:00:00", - "2022-08-22 02:00:00", - "2022-08-22 04:00:00", - "2022-08-22 06:00:00", - "2022-08-22 08:00:00", - "2022-08-22 10:00:00", - "2022-08-22 12:00:00", - "2022-08-22 14:00:00", - "2022-08-22 16:00:00", - "2022-08-22 18:00:00", - "2022-08-22 20:00:00", - "2022-08-22 22:00:00", - "2022-08-23 00:00:00", - "2022-08-23 02:00:00", - "2022-08-23 04:00:00", - "2022-08-23 06:00:00", - "2022-08-23 08:00:00", - "2022-08-23 10:00:00", - "2022-08-23 12:00:00", - "2022-08-23 14:00:00", - "2022-08-23 16:00:00", - "2022-08-23 18:00:00", - "2022-08-23 20:00:00", - "2022-08-23 22:00:00", - "2022-08-24 00:00:00", - "2022-08-24 02:00:00", - "2022-08-24 04:00:00", - "2022-08-24 06:00:00", - "2022-08-24 08:00:00", - "2022-08-24 10:00:00", - "2022-08-24 12:00:00", - "2022-08-24 14:00:00", - "2022-08-24 16:00:00", - "2022-08-24 18:00:00", - "2022-08-24 20:00:00", - "2022-08-24 22:00:00", - "2022-08-25 00:00:00", - "2022-08-25 02:00:00", - "2022-08-25 04:00:00", - "2022-08-25 06:00:00", - "2022-08-25 08:00:00", - "2022-08-25 10:00:00", - "2022-08-25 12:00:00", - "2022-08-25 14:00:00", - "2022-08-25 16:00:00", - "2022-08-25 18:00:00", - "2022-08-25 20:00:00", - "2022-08-25 22:00:00", - "2022-08-26 00:00:00", - "2022-08-26 02:00:00", - "2022-08-26 04:00:00", - "2022-08-26 06:00:00", - "2022-08-26 08:00:00", - "2022-08-26 10:00:00", - "2022-08-26 12:00:00", - "2022-08-26 14:00:00", - "2022-08-26 16:00:00", - "2022-08-26 18:00:00", - "2022-08-26 20:00:00", - "2022-08-26 22:00:00", - "2022-08-27 00:00:00", - "2022-08-27 02:00:00", - "2022-08-27 04:00:00", - "2022-08-27 06:00:00", - "2022-08-27 08:00:00", - "2022-08-27 10:00:00", - "2022-08-27 12:00:00", - "2022-08-27 14:00:00", - "2022-08-27 16:00:00", - "2022-08-27 18:00:00", - "2022-08-27 20:00:00", - "2022-08-27 22:00:00", - "2022-08-28 00:00:00", - "2022-08-28 02:00:00", - "2022-08-28 04:00:00", - "2022-08-28 06:00:00", - "2022-08-28 08:00:00", - "2022-08-28 10:00:00", - "2022-08-28 12:00:00", - "2022-08-28 14:00:00", - "2022-08-28 16:00:00", - "2022-08-28 18:00:00", - "2022-08-28 20:00:00", - "2022-08-28 22:00:00", - "2022-08-29 00:00:00", - "2022-08-29 02:00:00", - "2022-08-29 04:00:00", - "2022-08-29 06:00:00", - "2022-08-29 08:00:00", - "2022-08-29 10:00:00", - "2022-08-29 12:00:00", - "2022-08-29 14:00:00", - "2022-08-29 16:00:00", - "2022-08-29 18:00:00", - "2022-08-29 20:00:00", - "2022-08-29 22:00:00", - "2022-08-30 00:00:00", - "2022-08-30 02:00:00", - "2022-08-30 04:00:00", - "2022-08-30 06:00:00", - "2022-08-30 08:00:00", - "2022-08-30 10:00:00", - "2022-08-30 12:00:00", - "2022-08-30 14:00:00", - "2022-08-30 16:00:00", - "2022-08-30 18:00:00", - "2022-08-30 20:00:00", - "2022-08-30 22:00:00", - "2022-08-31 00:00:00", - "2022-08-31 02:00:00", - "2022-08-31 04:00:00", - "2022-08-31 06:00:00", - "2022-08-31 08:00:00", - "2022-08-31 10:00:00", - "2022-08-31 12:00:00", - "2022-08-31 14:00:00", - "2022-08-31 16:00:00", - "2022-08-31 18:00:00", - "2022-08-31 20:00:00", - "2022-08-31 22:00:00", - "2022-09-01 00:00:00", - "2022-09-01 02:00:00", - "2022-09-01 04:00:00", - "2022-09-01 06:00:00", - "2022-09-01 08:00:00", - "2022-09-01 10:00:00", - "2022-09-01 12:00:00", - "2022-09-01 14:00:00", - "2022-09-01 16:00:00", - "2022-09-01 18:00:00", - "2022-09-01 20:00:00", - "2022-09-01 22:00:00", - "2022-09-02 00:00:00", - "2022-09-02 02:00:00", - "2022-09-02 04:00:00", - "2022-09-02 06:00:00", - "2022-09-02 08:00:00", - "2022-09-02 10:00:00", - "2022-09-02 12:00:00", - "2022-09-02 14:00:00", - "2022-09-02 16:00:00", - "2022-09-02 18:00:00", - "2022-09-02 20:00:00", - "2022-09-02 22:00:00", - "2022-09-03 00:00:00", - "2022-09-03 02:00:00", - "2022-09-03 04:00:00", - "2022-09-03 06:00:00", - "2022-09-03 08:00:00", - "2022-09-03 10:00:00", - "2022-09-03 12:00:00", - "2022-09-03 14:00:00", - "2022-09-03 16:00:00", - "2022-09-03 18:00:00", - "2022-09-03 20:00:00", - "2022-09-03 22:00:00", - "2022-09-04 00:00:00", - "2022-09-04 02:00:00", - "2022-09-04 04:00:00", - "2022-09-04 06:00:00", - "2022-09-04 08:00:00", - "2022-09-04 10:00:00", - "2022-09-04 12:00:00", - "2022-09-04 14:00:00", - "2022-09-04 16:00:00", - "2022-09-04 18:00:00", - "2022-09-04 20:00:00", - "2022-09-04 22:00:00", - "2022-09-05 00:00:00", - "2022-09-05 02:00:00", - "2022-09-05 04:00:00", - "2022-09-05 06:00:00", - "2022-09-05 08:00:00", - "2022-09-05 10:00:00", - "2022-09-05 12:00:00", - "2022-09-05 14:00:00", - "2022-09-05 16:00:00", - "2022-09-05 18:00:00", - "2022-09-05 20:00:00", - "2022-09-05 22:00:00", - "2022-09-06 00:00:00", - "2022-09-06 02:00:00", - "2022-09-06 04:00:00", - "2022-09-06 06:00:00", - "2022-09-06 08:00:00", - "2022-09-06 10:00:00", - "2022-09-06 12:00:00", - "2022-09-06 14:00:00", - "2022-09-06 16:00:00", - "2022-09-06 18:00:00", - "2022-09-06 20:00:00", - "2022-09-06 22:00:00", - "2022-09-07 00:00:00", - "2022-09-07 02:00:00", - "2022-09-07 04:00:00", - "2022-09-07 06:00:00", - "2022-09-07 08:00:00", - "2022-09-07 10:00:00", - "2022-09-07 12:00:00", - "2022-09-07 14:00:00", - "2022-09-07 16:00:00", - "2022-09-07 18:00:00", - "2022-09-07 20:00:00", - "2022-09-07 22:00:00", - "2022-09-08 00:00:00", - "2022-09-08 02:00:00", - "2022-09-08 04:00:00", - "2022-09-08 06:00:00", - "2022-09-08 08:00:00", - "2022-09-08 10:00:00", - "2022-09-08 12:00:00", - "2022-09-08 14:00:00", - "2022-09-08 16:00:00", - "2022-09-08 18:00:00", - "2022-09-08 20:00:00", - "2022-09-08 22:00:00", - "2022-09-09 00:00:00", - "2022-09-09 02:00:00", - "2022-09-09 04:00:00", - "2022-09-09 06:00:00", - "2022-09-09 08:00:00", - "2022-09-09 10:00:00", - "2022-09-09 12:00:00", - "2022-09-09 14:00:00", - "2022-09-09 16:00:00", - "2022-09-09 18:00:00", - "2022-09-09 20:00:00", - "2022-09-09 22:00:00", - "2022-09-10 00:00:00", - "2022-09-10 02:00:00", - "2022-09-10 04:00:00", - "2022-09-10 06:00:00", - "2022-09-10 08:00:00", - "2022-09-10 10:00:00", - "2022-09-10 12:00:00", - "2022-09-10 14:00:00", - "2022-09-10 16:00:00", - "2022-09-10 18:00:00", - "2022-09-10 20:00:00", - "2022-09-10 22:00:00", - "2022-09-11 00:00:00", - "2022-09-11 02:00:00", - "2022-09-11 04:00:00", - "2022-09-11 06:00:00", - "2022-09-11 08:00:00", - "2022-09-11 10:00:00", - "2022-09-11 12:00:00", - "2022-09-11 14:00:00", - "2022-09-11 16:00:00", - "2022-09-11 18:00:00", - "2022-09-11 20:00:00", - "2022-09-11 22:00:00", - "2022-09-12 00:00:00", - "2022-09-12 02:00:00", - "2022-09-12 04:00:00", - "2022-09-12 06:00:00", - "2022-09-12 08:00:00", - "2022-09-12 10:00:00", - "2022-09-12 12:00:00", - "2022-09-12 14:00:00", - "2022-09-12 16:00:00", - "2022-09-12 18:00:00", - "2022-09-12 20:00:00", - "2022-09-12 22:00:00", - "2022-09-13 00:00:00", - "2022-09-13 02:00:00", - "2022-09-13 04:00:00", - "2022-09-13 06:00:00", - "2022-09-13 08:00:00", - "2022-09-13 10:00:00", - "2022-09-13 12:00:00", - "2022-09-13 14:00:00", - "2022-09-13 16:00:00", - "2022-09-13 18:00:00", - "2022-09-13 20:00:00", - "2022-09-13 22:00:00", - "2022-09-14 00:00:00", - "2022-09-14 02:00:00", - "2022-09-14 04:00:00", - "2022-09-14 06:00:00", - "2022-09-14 08:00:00", - "2022-09-14 10:00:00", - "2022-09-14 12:00:00", - "2022-09-14 14:00:00", - "2022-09-14 16:00:00", - "2022-09-14 18:00:00", - "2022-09-14 20:00:00", - "2022-09-14 22:00:00", - "2022-09-15 00:00:00", - "2022-09-15 02:00:00", - "2022-09-15 04:00:00", - "2022-09-15 06:00:00", - "2022-09-15 08:00:00", - "2022-09-15 10:00:00", - "2022-09-15 12:00:00", - "2022-09-15 14:00:00", - "2022-09-15 16:00:00", - "2022-09-15 18:00:00", - "2022-09-15 20:00:00", - "2022-09-15 22:00:00", - "2022-09-16 00:00:00", - "2022-09-16 02:00:00", - "2022-09-16 04:00:00", - "2022-09-16 06:00:00", - "2022-09-16 08:00:00", - "2022-09-16 10:00:00", - "2022-09-16 12:00:00", - "2022-09-16 14:00:00", - "2022-09-16 16:00:00", - "2022-09-16 18:00:00", - "2022-09-16 20:00:00", - "2022-09-16 22:00:00", - "2022-09-17 00:00:00", - "2022-09-17 02:00:00", - "2022-09-17 04:00:00", - "2022-09-17 06:00:00", - "2022-09-17 08:00:00", - "2022-09-17 10:00:00", - "2022-09-17 12:00:00", - "2022-09-17 14:00:00", - "2022-09-17 16:00:00", - "2022-09-17 18:00:00", - "2022-09-17 20:00:00", - "2022-09-17 22:00:00", - "2022-09-18 00:00:00", - "2022-09-18 02:00:00", - "2022-09-18 04:00:00", - "2022-09-18 06:00:00", - "2022-09-18 08:00:00", - "2022-09-18 10:00:00", - "2022-09-18 12:00:00", - "2022-09-18 14:00:00", - "2022-09-18 16:00:00", - "2022-09-18 18:00:00", - "2022-09-18 20:00:00", - "2022-09-18 22:00:00", - "2022-09-19 00:00:00", - "2022-09-19 02:00:00", - "2022-09-19 04:00:00", - "2022-09-19 06:00:00", - "2022-09-19 08:00:00", - "2022-09-19 10:00:00", - "2022-09-19 12:00:00", - "2022-09-19 14:00:00", - "2022-09-19 16:00:00", - "2022-09-19 18:00:00", - "2022-09-19 20:00:00", - "2022-09-19 22:00:00", - "2022-09-20 00:00:00", - "2022-09-20 02:00:00", - "2022-09-20 04:00:00", - "2022-09-20 06:00:00", - "2022-09-20 08:00:00", - "2022-09-20 10:00:00", - "2022-09-20 12:00:00", - "2022-09-20 14:00:00", - "2022-09-20 16:00:00", - "2022-09-20 18:00:00", - "2022-09-20 20:00:00", - "2022-09-20 22:00:00", - "2022-09-21 00:00:00", - "2022-09-21 02:00:00", - "2022-09-21 04:00:00", - "2022-09-21 06:00:00", - "2022-09-21 08:00:00", - "2022-09-21 10:00:00", - "2022-09-21 12:00:00", - "2022-09-21 14:00:00", - "2022-09-21 16:00:00", - "2022-09-21 18:00:00", - "2022-09-21 20:00:00", - "2022-09-21 22:00:00", - "2022-09-22 00:00:00", - "2022-09-22 02:00:00", - "2022-09-22 04:00:00", - "2022-09-22 06:00:00", - "2022-09-22 08:00:00", - "2022-09-22 10:00:00", - "2022-09-22 12:00:00", - "2022-09-22 14:00:00", - "2022-09-22 16:00:00", - "2022-09-22 18:00:00", - "2022-09-22 20:00:00", - "2022-09-22 22:00:00", - "2022-09-23 00:00:00", - "2022-09-23 02:00:00", - "2022-09-23 04:00:00", - "2022-09-23 06:00:00", - "2022-09-23 08:00:00", - "2022-09-23 10:00:00", - "2022-09-23 12:00:00", - "2022-09-23 14:00:00", - "2022-09-23 16:00:00", - "2022-09-23 18:00:00", - "2022-09-23 20:00:00", - "2022-09-23 22:00:00", - "2022-09-24 00:00:00", - "2022-09-24 02:00:00", - "2022-09-24 04:00:00", - "2022-09-24 06:00:00", - "2022-09-24 08:00:00", - "2022-09-24 10:00:00", - "2022-09-24 12:00:00", - "2022-09-24 14:00:00", - "2022-09-24 16:00:00", - "2022-09-24 18:00:00", - "2022-09-24 20:00:00", - "2022-09-24 22:00:00", - "2022-09-25 00:00:00", - "2022-09-25 02:00:00", - "2022-09-25 04:00:00", - "2022-09-25 06:00:00", - "2022-09-25 08:00:00", - "2022-09-25 10:00:00", - "2022-09-25 12:00:00", - "2022-09-25 14:00:00", - "2022-09-25 16:00:00", - "2022-09-25 18:00:00", - "2022-09-25 20:00:00", - "2022-09-25 22:00:00", - "2022-09-26 00:00:00", - "2022-09-26 02:00:00", - "2022-09-26 04:00:00", - "2022-09-26 06:00:00", - "2022-09-26 08:00:00", - "2022-09-26 10:00:00", - "2022-09-26 12:00:00", - "2022-09-26 14:00:00", - "2022-09-26 16:00:00", - "2022-09-26 18:00:00", - "2022-09-26 20:00:00", - "2022-09-26 22:00:00", - "2022-09-27 00:00:00", - "2022-09-27 02:00:00", - "2022-09-27 04:00:00", - "2022-09-27 06:00:00", - "2022-09-27 08:00:00", - "2022-09-27 10:00:00", - "2022-09-27 12:00:00", - "2022-09-27 14:00:00", - "2022-09-27 16:00:00", - "2022-09-27 18:00:00", - "2022-09-27 20:00:00", - "2022-09-27 22:00:00", - "2022-09-28 00:00:00", - "2022-09-28 02:00:00", - "2022-09-28 04:00:00", - "2022-09-28 06:00:00", - "2022-09-28 08:00:00", - "2022-09-28 10:00:00", - "2022-09-28 12:00:00", - "2022-09-28 14:00:00", - "2022-09-28 16:00:00", - "2022-09-28 18:00:00", - "2022-09-28 20:00:00", - "2022-09-28 22:00:00", - "2022-09-29 00:00:00", - "2022-09-29 02:00:00", - "2022-09-29 04:00:00", - "2022-09-29 06:00:00", - "2022-09-29 08:00:00", - "2022-09-29 10:00:00", - "2022-09-29 12:00:00", - "2022-09-29 14:00:00", - "2022-09-29 16:00:00", - "2022-09-29 18:00:00", - "2022-09-29 20:00:00", - "2022-09-29 22:00:00", - "2022-09-30 00:00:00", - "2022-09-30 02:00:00", - "2022-09-30 04:00:00", - "2022-09-30 06:00:00", - "2022-09-30 08:00:00", - "2022-09-30 10:00:00", - "2022-09-30 12:00:00", - "2022-09-30 14:00:00", - "2022-09-30 16:00:00", - "2022-09-30 18:00:00", - "2022-09-30 20:00:00", - "2022-09-30 22:00:00", - "2022-10-01 00:00:00", - "2022-10-01 02:00:00", - "2022-10-01 04:00:00", - "2022-10-01 06:00:00", - "2022-10-01 08:00:00", - "2022-10-01 10:00:00", - "2022-10-01 12:00:00", - "2022-10-01 14:00:00", - "2022-10-01 16:00:00", - "2022-10-01 18:00:00", - "2022-10-01 20:00:00", - "2022-10-01 22:00:00", - "2022-10-02 00:00:00", - "2022-10-02 02:00:00", - "2022-10-02 04:00:00", - "2022-10-02 06:00:00", - "2022-10-02 08:00:00", - "2022-10-02 10:00:00", - "2022-10-02 12:00:00", - "2022-10-02 14:00:00", - "2022-10-02 16:00:00", - "2022-10-02 18:00:00", - "2022-10-02 20:00:00", - "2022-10-02 22:00:00", - "2022-10-03 00:00:00", - "2022-10-03 02:00:00", - "2022-10-03 04:00:00", - "2022-10-03 06:00:00", - "2022-10-03 08:00:00", - "2022-10-03 10:00:00", - "2022-10-03 12:00:00", - "2022-10-03 14:00:00", - "2022-10-03 16:00:00", - "2022-10-03 18:00:00", - "2022-10-03 20:00:00", - "2022-10-03 22:00:00", - "2022-10-04 00:00:00", - "2022-10-04 02:00:00", - "2022-10-04 04:00:00", - "2022-10-04 06:00:00", - "2022-10-04 08:00:00", - "2022-10-04 10:00:00", - "2022-10-04 12:00:00", - "2022-10-04 14:00:00", - "2022-10-04 16:00:00", - "2022-10-04 18:00:00", - "2022-10-04 20:00:00", - "2022-10-04 22:00:00", - "2022-10-05 00:00:00", - "2022-10-05 02:00:00", - "2022-10-05 04:00:00", - "2022-10-05 06:00:00", - "2022-10-05 08:00:00", - "2022-10-05 10:00:00", - "2022-10-05 12:00:00", - "2022-10-05 14:00:00", - "2022-10-05 16:00:00", - "2022-10-05 18:00:00", - "2022-10-05 20:00:00", - "2022-10-05 22:00:00", - "2022-10-06 00:00:00", - "2022-10-06 02:00:00", - "2022-10-06 04:00:00", - "2022-10-06 06:00:00", - "2022-10-06 08:00:00", - "2022-10-06 10:00:00", - "2022-10-06 12:00:00", - "2022-10-06 14:00:00", - "2022-10-06 16:00:00", - "2022-10-06 18:00:00", - "2022-10-06 20:00:00", - "2022-10-06 22:00:00", - "2022-10-07 00:00:00", - "2022-10-07 02:00:00", - "2022-10-07 04:00:00", - "2022-10-07 06:00:00", - "2022-10-07 08:00:00", - "2022-10-07 10:00:00", - "2022-10-07 12:00:00", - "2022-10-07 14:00:00", - "2022-10-07 16:00:00", - "2022-10-07 18:00:00", - "2022-10-07 20:00:00", - "2022-10-07 22:00:00", - "2022-10-08 00:00:00", - "2022-10-08 04:00:00", - "2022-10-08 06:00:00", - "2022-10-08 08:00:00", - "2022-10-08 10:00:00", - "2022-10-08 12:00:00", - "2022-10-08 14:00:00", - "2022-10-08 16:00:00", - "2022-10-08 18:00:00", - "2022-10-08 20:00:00", - "2022-10-08 22:00:00", - "2022-10-09 00:00:00", - "2022-10-09 02:00:00", - "2022-10-09 04:00:00", - "2022-10-09 06:00:00", - "2022-10-09 08:00:00", - "2022-10-09 10:00:00", - "2022-10-09 12:00:00", - "2022-10-09 14:00:00", - "2022-10-09 16:00:00", - "2022-10-09 18:00:00", - "2022-10-09 20:00:00", - "2022-10-09 22:00:00", - "2022-10-10 00:00:00", - "2022-10-10 02:00:00", - "2022-10-10 04:00:00", - "2022-10-10 06:00:00", - "2022-10-10 08:00:00", - "2022-10-10 10:00:00", - "2022-10-10 12:00:00", - "2022-10-10 14:00:00", - "2022-10-10 16:00:00", - "2022-10-10 18:00:00", - "2022-10-10 20:00:00", - "2022-10-10 22:00:00", - "2022-10-11 00:00:00", - "2022-10-11 02:00:00", - "2022-10-11 04:00:00", - "2022-10-11 06:00:00", - "2022-10-11 08:00:00", - "2022-10-11 10:00:00", - "2022-10-11 12:00:00", - "2022-10-11 14:00:00", - "2022-10-11 16:00:00", - "2022-10-11 18:00:00", - "2022-10-11 20:00:00", - "2022-10-11 22:00:00", - "2022-10-12 00:00:00", - "2022-10-12 02:00:00", - "2022-10-12 04:00:00", - "2022-10-12 06:00:00", - "2022-10-12 08:00:00", - "2022-10-12 10:00:00", - "2022-10-12 12:00:00", - "2022-10-12 14:00:00", - "2022-10-12 16:00:00", - "2022-10-12 18:00:00", - "2022-10-12 20:00:00", - "2022-10-12 22:00:00", - "2022-10-13 00:00:00", - "2022-10-13 02:00:00", - "2022-10-13 04:00:00", - "2022-10-13 06:00:00", - "2022-10-13 08:00:00", - "2022-10-13 10:00:00", - "2022-10-13 12:00:00", - "2022-10-13 14:00:00", - "2022-10-13 16:00:00", - "2022-10-13 18:00:00", - "2022-10-13 20:00:00", - "2022-10-13 22:00:00", - "2022-10-14 00:00:00", - "2022-10-14 02:00:00", - "2022-10-14 04:00:00", - "2022-10-14 06:00:00", - "2022-10-14 08:00:00", - "2022-10-14 10:00:00", - "2022-10-14 12:00:00", - "2022-10-14 14:00:00", - "2022-10-14 16:00:00", - "2022-10-14 18:00:00", - "2022-10-14 20:00:00", - "2022-10-14 22:00:00", - "2022-10-15 00:00:00", - "2022-10-15 02:00:00", - "2022-10-15 04:00:00", - "2022-10-15 06:00:00", - "2022-10-15 08:00:00", - "2022-10-15 10:00:00", - "2022-10-15 12:00:00", - "2022-10-15 14:00:00", - "2022-10-15 16:00:00", - "2022-10-15 18:00:00", - "2022-10-15 20:00:00", - "2022-10-15 22:00:00", - "2022-10-16 00:00:00", - "2022-10-16 02:00:00", - "2022-10-16 04:00:00", - "2022-10-16 06:00:00", - "2022-10-16 08:00:00", - "2022-10-16 10:00:00", - "2022-10-16 12:00:00", - "2022-10-16 14:00:00", - "2022-10-16 16:00:00", - "2022-10-16 18:00:00", - "2022-10-16 20:00:00", - "2022-10-16 22:00:00", - "2022-10-17 00:00:00", - "2022-10-17 02:00:00", - "2022-10-17 04:00:00", - "2022-10-17 06:00:00", - "2022-10-17 08:00:00", - "2022-10-17 10:00:00", - "2022-10-17 12:00:00", - "2022-10-17 14:00:00", - "2022-10-17 16:00:00", - "2022-10-17 18:00:00", - "2022-10-17 20:00:00", - "2022-10-17 22:00:00", - "2022-10-18 00:00:00", - "2022-10-18 02:00:00", - "2022-10-18 04:00:00", - "2022-10-18 06:00:00", - "2022-10-18 08:00:00", - "2022-10-18 10:00:00", - "2022-10-18 12:00:00", - "2022-10-18 14:00:00", - "2022-10-18 16:00:00", - "2022-10-18 18:00:00", - "2022-10-18 20:00:00", - "2022-10-18 22:00:00", - "2022-10-19 00:00:00", - "2022-10-19 02:00:00", - "2022-10-19 04:00:00", - "2022-10-19 06:00:00", - "2022-10-19 08:00:00", - "2022-10-19 10:00:00", - "2022-10-19 12:00:00", - "2022-10-19 14:00:00", - "2022-10-19 16:00:00", - "2022-10-19 18:00:00", - "2022-10-19 20:00:00", - "2022-10-19 22:00:00", - "2022-10-20 00:00:00", - "2022-10-20 02:00:00", - "2022-10-20 04:00:00", - "2022-10-20 06:00:00", - "2022-10-20 08:00:00", - "2022-10-20 10:00:00", - "2022-10-20 12:00:00", - "2022-10-20 14:00:00", - "2022-10-20 16:00:00", - "2022-10-20 18:00:00", - "2022-10-20 20:00:00", - "2022-10-20 22:00:00", - "2022-10-21 00:00:00", - "2022-10-21 02:00:00", - "2022-10-21 04:00:00", - "2022-10-21 06:00:00", - "2022-10-21 08:00:00", - "2022-10-21 10:00:00", - "2022-10-21 12:00:00", - "2022-10-21 14:00:00", - "2022-10-21 16:00:00", - "2022-10-21 18:00:00", - "2022-10-21 20:00:00", - "2022-10-21 22:00:00", - "2022-10-22 00:00:00", - "2022-10-22 02:00:00", - "2022-10-22 04:00:00", - "2022-10-22 06:00:00", - "2022-10-22 08:00:00", - "2022-10-22 10:00:00", - "2022-10-22 12:00:00", - "2022-10-22 14:00:00", - "2022-10-22 16:00:00", - "2022-10-22 18:00:00", - "2022-10-22 20:00:00", - "2022-10-22 22:00:00", - "2022-10-23 00:00:00", - "2022-10-23 02:00:00", - "2022-10-23 04:00:00", - "2022-10-23 06:00:00", - "2022-10-23 08:00:00", - "2022-10-23 10:00:00", - "2022-10-23 12:00:00", - "2022-10-23 14:00:00", - "2022-10-23 16:00:00", - "2022-10-23 18:00:00", - "2022-10-23 20:00:00", - "2022-10-23 22:00:00", - "2022-10-24 00:00:00", - "2022-10-24 02:00:00", - "2022-10-24 04:00:00", - "2022-10-24 06:00:00", - "2022-10-24 08:00:00", - "2022-10-24 10:00:00", - "2022-10-24 12:00:00", - "2022-10-24 14:00:00", - "2022-10-24 16:00:00", - "2022-10-24 18:00:00", - "2022-10-24 20:00:00", - "2022-10-24 22:00:00", - "2022-10-25 00:00:00", - "2022-10-25 02:00:00", - "2022-10-25 04:00:00", - "2022-10-25 06:00:00", - "2022-10-25 08:00:00", - "2022-10-25 10:00:00", - "2022-10-25 12:00:00", - "2022-10-25 14:00:00", - "2022-10-25 16:00:00", - "2022-10-25 18:00:00", - "2022-10-25 20:00:00", - "2022-10-25 22:00:00", - "2022-10-26 00:00:00", - "2022-10-26 02:00:00", - "2022-10-26 04:00:00", - "2022-10-26 06:00:00", - "2022-10-26 08:00:00", - "2022-10-26 10:00:00", - "2022-10-26 12:00:00", - "2022-10-26 14:00:00", - "2022-10-26 16:00:00", - "2022-10-26 18:00:00", - "2022-10-26 20:00:00", - "2022-10-26 22:00:00", - "2022-10-27 00:00:00", - "2022-10-27 02:00:00", - "2022-10-27 04:00:00", - "2022-10-27 06:00:00", - "2022-10-27 08:00:00", - "2022-10-27 10:00:00", - "2022-10-27 12:00:00", - "2022-10-27 14:00:00", - "2022-10-27 16:00:00", - "2022-10-27 18:00:00", - "2022-10-27 20:00:00", - "2022-10-27 22:00:00", - "2022-10-28 00:00:00", - "2022-10-28 02:00:00", - "2022-10-28 04:00:00", - "2022-10-28 06:00:00", - "2022-10-28 08:00:00", - "2022-10-28 10:00:00", - "2022-10-28 12:00:00", - "2022-10-28 14:00:00", - "2022-10-28 16:00:00", - "2022-10-28 18:00:00", - "2022-10-28 20:00:00", - "2022-10-28 22:00:00", - "2022-10-29 00:00:00", - "2022-10-29 02:00:00", - "2022-10-29 04:00:00", - "2022-10-29 06:00:00", - "2022-10-29 08:00:00", - "2022-10-29 10:00:00", - "2022-10-29 12:00:00", - "2022-10-29 14:00:00", - "2022-10-29 16:00:00", - "2022-10-29 18:00:00", - "2022-10-29 20:00:00", - "2022-10-29 22:00:00", - "2022-10-30 00:00:00", - "2022-10-30 02:00:00", - "2022-10-30 04:00:00", - "2022-10-30 06:00:00", - "2022-10-30 08:00:00", - "2022-10-30 10:00:00", - "2022-10-30 12:00:00", - "2022-10-30 14:00:00", - "2022-10-30 16:00:00", - "2022-10-30 18:00:00", - "2022-10-30 20:00:00", - "2022-10-30 22:00:00", - "2022-10-31 00:00:00", - "2022-10-31 02:00:00", - "2022-10-31 04:00:00", - "2022-10-31 06:00:00", - "2022-10-31 08:00:00", - "2022-10-31 10:00:00", - "2022-10-31 12:00:00", - "2022-10-31 14:00:00", - "2022-10-31 16:00:00", - "2022-10-31 18:00:00", - "2022-10-31 20:00:00", - "2022-10-31 22:00:00", - "2022-11-01 00:00:00", - "2022-11-01 02:00:00", - "2022-11-01 04:00:00", - "2022-11-01 06:00:00", - "2022-11-01 08:00:00", - "2022-11-01 10:00:00", - "2022-11-01 12:00:00", - "2022-11-01 14:00:00", - "2022-11-01 16:00:00", - "2022-11-01 18:00:00", - "2022-11-01 20:00:00", - "2022-11-01 22:00:00", - "2022-11-02 00:00:00", - "2022-11-02 02:00:00", - "2022-11-02 04:00:00", - "2022-11-02 06:00:00", - "2022-11-02 08:00:00", - "2022-11-02 10:00:00", - "2022-11-02 12:00:00", - "2022-11-02 14:00:00", - "2022-11-02 16:00:00", - "2022-11-02 18:00:00", - "2022-11-02 20:00:00", - "2022-11-02 22:00:00", - "2022-11-03 00:00:00", - "2022-11-03 02:00:00", - "2022-11-03 04:00:00", - "2022-11-03 06:00:00", - "2022-11-03 08:00:00", - "2022-11-03 10:00:00", - "2022-11-03 12:00:00", - "2022-11-03 14:00:00", - "2022-11-03 16:00:00", - "2022-11-03 18:00:00", - "2022-11-03 20:00:00", - "2022-11-03 22:00:00", - "2022-11-04 00:00:00", - "2022-11-04 02:00:00", - "2022-11-04 04:00:00", - "2022-11-04 06:00:00", - "2022-11-04 08:00:00", - "2022-11-04 10:00:00", - "2022-11-04 12:00:00", - "2022-11-04 14:00:00", - "2022-11-04 16:00:00", - "2022-11-04 18:00:00", - "2022-11-04 20:00:00", - "2022-11-04 22:00:00", - "2022-11-05 00:00:00", - "2022-11-05 02:00:00", - "2022-11-05 04:00:00", - "2022-11-05 06:00:00", - "2022-11-05 08:00:00", - "2022-11-05 10:00:00", - "2022-11-05 12:00:00", - "2022-11-05 14:00:00", - "2022-11-05 16:00:00", - "2022-11-05 18:00:00", - "2022-11-05 20:00:00", - "2022-11-05 22:00:00", - "2022-11-06 00:00:00", - "2022-11-06 02:00:00", - "2022-11-06 04:00:00", - "2022-11-06 06:00:00", - "2022-11-06 08:00:00", - "2022-11-06 10:00:00", - "2022-11-06 12:00:00", - "2022-11-06 14:00:00", - "2022-11-06 16:00:00", - "2022-11-06 18:00:00", - "2022-11-06 20:00:00", - "2022-11-06 22:00:00", - "2022-11-07 00:00:00", - "2022-11-07 02:00:00", - "2022-11-07 04:00:00", - "2022-11-07 06:00:00", - "2022-11-07 08:00:00", - "2022-11-07 10:00:00", - "2022-11-07 12:00:00", - "2022-11-07 14:00:00", - "2022-11-07 16:00:00", - "2022-11-07 18:00:00", - "2022-11-07 20:00:00", - "2022-11-07 22:00:00", - "2022-11-08 00:00:00", - "2022-11-08 02:00:00", - "2022-11-08 04:00:00", - "2022-11-08 06:00:00", - "2022-11-08 08:00:00", - "2022-11-08 10:00:00", - "2022-11-08 12:00:00", - "2022-11-08 14:00:00", - "2022-11-08 16:00:00", - "2022-11-08 18:00:00", - "2022-11-08 20:00:00", - "2022-11-08 22:00:00", - "2022-11-09 00:00:00", - "2022-11-09 02:00:00", - "2022-11-09 04:00:00", - "2022-11-09 06:00:00", - "2022-11-09 08:00:00", - "2022-11-09 10:00:00", - "2022-11-09 12:00:00", - "2022-11-09 14:00:00", - "2022-11-09 16:00:00", - "2022-11-09 18:00:00", - "2022-11-09 20:00:00", - "2022-11-09 22:00:00", - "2022-11-10 00:00:00", - "2022-11-10 02:00:00", - "2022-11-10 04:00:00", - "2022-11-10 06:00:00", - "2022-11-10 08:00:00", - "2022-11-10 10:00:00", - "2022-11-10 12:00:00", - "2022-11-10 14:00:00", - "2022-11-10 16:00:00", - "2022-11-10 18:00:00", - "2022-11-10 20:00:00", - "2022-11-10 22:00:00", - "2022-11-11 00:00:00", - "2022-11-11 02:00:00", - "2022-11-11 04:00:00", - "2022-11-11 06:00:00", - "2022-11-11 08:00:00", - "2022-11-11 10:00:00", - "2022-11-11 12:00:00", - "2022-11-11 14:00:00", - "2022-11-11 16:00:00", - "2022-11-11 18:00:00", - "2022-11-11 20:00:00", - "2022-11-11 22:00:00", - "2022-11-12 00:00:00", - "2022-11-12 02:00:00", - "2022-11-12 04:00:00", - "2022-11-12 06:00:00", - "2022-11-12 08:00:00", - "2022-11-12 10:00:00", - "2022-11-12 12:00:00", - "2022-11-12 14:00:00", - "2022-11-12 16:00:00", - "2022-11-12 18:00:00", - "2022-11-12 20:00:00", - "2022-11-12 22:00:00", - "2022-11-13 00:00:00", - "2022-11-13 02:00:00", - "2022-11-13 04:00:00", - "2022-11-13 06:00:00", - "2022-11-13 08:00:00", - "2022-11-13 10:00:00", - "2022-11-13 12:00:00", - "2022-11-13 14:00:00", - "2022-11-13 16:00:00", - "2022-11-13 18:00:00", - "2022-11-13 20:00:00", - "2022-11-13 22:00:00", - "2022-11-14 00:00:00", - "2022-11-14 02:00:00", - "2022-11-14 04:00:00", - "2022-11-14 06:00:00", - "2022-11-14 08:00:00", - "2022-11-14 10:00:00", - "2022-11-14 12:00:00", - "2022-11-14 14:00:00", - "2022-11-14 16:00:00", - "2022-11-14 18:00:00", - "2022-11-14 20:00:00", - "2022-11-14 22:00:00", - "2022-11-15 00:00:00", - "2022-11-15 02:00:00", - "2022-11-15 04:00:00", - "2022-11-15 06:00:00", - "2022-11-15 08:00:00", - "2022-11-15 10:00:00", - "2022-11-15 12:00:00", - "2022-11-15 14:00:00", - "2022-11-15 16:00:00", - "2022-11-15 18:00:00", - "2022-11-15 20:00:00", - "2022-11-15 22:00:00", - "2022-11-16 00:00:00", - "2022-11-16 02:00:00", - "2022-11-16 04:00:00", - "2022-11-16 06:00:00", - "2022-11-16 08:00:00", - "2022-11-16 10:00:00", - "2022-11-16 12:00:00", - "2022-11-16 14:00:00", - "2022-11-16 16:00:00", - "2022-11-16 18:00:00", - "2022-11-16 20:00:00", - "2022-11-16 22:00:00", - "2022-11-17 00:00:00", - "2022-11-17 02:00:00", - "2022-11-17 04:00:00", - "2022-11-17 06:00:00", - "2022-11-17 08:00:00", - "2022-11-17 10:00:00", - "2022-11-17 12:00:00", - "2022-11-17 14:00:00", - "2022-11-17 16:00:00", - "2022-11-17 18:00:00", - "2022-11-17 20:00:00", - "2022-11-17 22:00:00", - "2022-11-18 00:00:00", - "2022-11-18 02:00:00", - "2022-11-18 04:00:00", - "2022-11-18 06:00:00", - "2022-11-18 08:00:00", - "2022-11-18 10:00:00", - "2022-11-18 12:00:00", - "2022-11-18 14:00:00", - "2022-11-18 16:00:00", - "2022-11-18 18:00:00", - "2022-11-18 20:00:00", - "2022-11-18 22:00:00", - "2022-11-19 00:00:00", - "2022-11-19 02:00:00", - "2022-11-19 04:00:00", - "2022-11-19 06:00:00", - "2022-11-19 08:00:00", - "2022-11-19 10:00:00", - "2022-11-19 12:00:00", - "2022-11-19 14:00:00", - "2022-11-19 16:00:00", - "2022-11-19 18:00:00", - "2022-11-19 20:00:00", - "2022-11-19 22:00:00", - "2022-11-20 00:00:00", - "2022-11-20 02:00:00", - "2022-11-20 04:00:00", - "2022-11-20 06:00:00", - "2022-11-20 08:00:00", - "2022-11-20 10:00:00", - "2022-11-20 12:00:00", - "2022-11-20 14:00:00", - "2022-11-20 16:00:00", - "2022-11-20 18:00:00", - "2022-11-20 20:00:00", - "2022-11-20 22:00:00", - "2022-11-21 00:00:00", - "2022-11-21 02:00:00", - "2022-11-21 04:00:00", - "2022-11-21 06:00:00", - "2022-11-21 08:00:00", - "2022-11-21 10:00:00", - "2022-11-21 12:00:00", - "2022-11-21 14:00:00", - "2022-11-21 16:00:00", - "2022-11-21 18:00:00", - "2022-11-21 20:00:00", - "2022-11-21 22:00:00", - "2022-11-22 00:00:00", - "2022-11-22 02:00:00", - "2022-11-22 04:00:00", - "2022-11-22 06:00:00", - "2022-11-22 08:00:00", - "2022-11-22 10:00:00", - "2022-11-22 12:00:00", - "2022-11-22 14:00:00", - "2022-11-22 16:00:00", - "2022-11-22 18:00:00", - "2022-11-22 20:00:00", - "2022-11-22 22:00:00", - "2022-11-23 00:00:00", - "2022-11-23 02:00:00", - "2022-11-23 04:00:00", - "2022-11-23 06:00:00", - "2022-11-23 08:00:00", - "2022-11-23 10:00:00", - "2022-11-23 12:00:00", - "2022-11-23 14:00:00", - "2022-11-23 16:00:00", - "2022-11-23 18:00:00", - "2022-11-23 20:00:00", - "2022-11-23 22:00:00", - "2022-11-24 00:00:00", - "2022-11-24 02:00:00", - "2022-11-24 04:00:00", - "2022-11-24 06:00:00", - "2022-11-24 08:00:00", - "2022-11-24 10:00:00", - "2022-11-24 12:00:00", - "2022-11-24 14:00:00", - "2022-11-24 16:00:00", - "2022-11-24 18:00:00", - "2022-11-24 20:00:00", - "2022-11-24 22:00:00", - "2022-11-25 00:00:00", - "2022-11-25 02:00:00", - "2022-11-25 04:00:00", - "2022-11-25 06:00:00", - "2022-11-25 08:00:00", - "2022-11-25 10:00:00", - "2022-11-25 12:00:00", - "2022-11-25 14:00:00", - "2022-11-25 16:00:00", - "2022-11-25 18:00:00", - "2022-11-25 20:00:00", - "2022-11-25 22:00:00", - "2022-11-26 00:00:00", - "2022-11-26 02:00:00", - "2022-11-26 04:00:00", - "2022-11-26 06:00:00", - "2022-11-26 08:00:00", - "2022-11-26 10:00:00", - "2022-11-26 12:00:00", - "2022-11-26 14:00:00", - "2022-11-26 16:00:00", - "2022-11-26 18:00:00", - "2022-11-26 20:00:00", - "2022-11-26 22:00:00", - "2022-11-27 00:00:00", - "2022-11-27 02:00:00", - "2022-11-27 04:00:00", - "2022-11-27 06:00:00", - "2022-11-27 08:00:00", - "2022-11-27 10:00:00", - "2022-11-27 12:00:00", - "2022-11-27 14:00:00", - "2022-11-27 16:00:00", - "2022-11-27 18:00:00", - "2022-11-27 20:00:00", - "2022-11-27 22:00:00", - "2022-11-28 00:00:00", - "2022-11-28 02:00:00", - "2022-11-28 04:00:00", - "2022-11-28 06:00:00", - "2022-11-28 08:00:00", - "2022-11-28 10:00:00", - "2022-11-28 12:00:00", - "2022-11-28 14:00:00", - "2022-11-28 16:00:00", - "2022-11-28 18:00:00", - "2022-11-28 20:00:00", - "2022-11-28 22:00:00", - "2022-11-29 00:00:00", - "2022-11-29 02:00:00", - "2022-11-29 04:00:00", - "2022-11-29 06:00:00", - "2022-11-29 08:00:00", - "2022-11-29 10:00:00", - "2022-11-29 12:00:00", - "2022-11-29 14:00:00", - "2022-11-29 16:00:00", - "2022-11-29 18:00:00", - "2022-11-29 20:00:00", - "2022-11-29 22:00:00", - "2022-11-30 00:00:00", - "2022-11-30 02:00:00", - "2022-11-30 04:00:00", - "2022-11-30 06:00:00", - "2022-11-30 08:00:00", - "2022-11-30 10:00:00", - "2022-11-30 12:00:00", - "2022-11-30 14:00:00", - "2022-11-30 16:00:00", - "2022-11-30 18:00:00", - "2022-11-30 20:00:00", - "2022-11-30 22:00:00", - "2022-12-01 00:00:00", - "2022-12-01 02:00:00", - "2022-12-01 04:00:00", - "2022-12-01 06:00:00", - "2022-12-01 08:00:00", - "2022-12-01 10:00:00", - "2022-12-01 12:00:00", - "2022-12-01 14:00:00", - "2022-12-01 16:00:00", - "2022-12-01 18:00:00", - "2022-12-01 20:00:00", - "2022-12-01 22:00:00", - "2022-12-02 00:00:00", - "2022-12-02 02:00:00", - "2022-12-02 04:00:00", - "2022-12-02 06:00:00", - "2022-12-02 08:00:00", - "2022-12-02 10:00:00", - "2022-12-02 12:00:00", - "2022-12-02 14:00:00", - "2022-12-02 16:00:00", - "2022-12-02 18:00:00", - "2022-12-02 20:00:00", - "2022-12-02 22:00:00", - "2022-12-03 00:00:00", - "2022-12-03 02:00:00", - "2022-12-03 04:00:00", - "2022-12-03 06:00:00", - "2022-12-03 08:00:00", - "2022-12-03 10:00:00", - "2022-12-03 12:00:00", - "2022-12-03 14:00:00", - "2022-12-03 16:00:00", - "2022-12-03 18:00:00", - "2022-12-03 20:00:00", - "2022-12-03 22:00:00", - "2022-12-04 00:00:00", - "2022-12-04 02:00:00", - "2022-12-04 04:00:00", - "2022-12-04 06:00:00", - "2022-12-04 08:00:00", - "2022-12-04 10:00:00", - "2022-12-04 12:00:00", - "2022-12-04 14:00:00", - "2022-12-04 16:00:00", - "2022-12-04 18:00:00", - "2022-12-04 20:00:00", - "2022-12-04 22:00:00", - "2022-12-05 00:00:00", - "2022-12-05 02:00:00", - "2022-12-05 04:00:00", - "2022-12-05 06:00:00", - "2022-12-05 08:00:00", - "2022-12-05 10:00:00", - "2022-12-05 12:00:00", - "2022-12-05 14:00:00", - "2022-12-05 16:00:00", - "2022-12-05 18:00:00", - "2022-12-05 20:00:00", - "2022-12-05 22:00:00", - "2022-12-06 00:00:00", - "2022-12-06 02:00:00", - "2022-12-06 04:00:00", - "2022-12-06 06:00:00", - "2022-12-06 08:00:00", - "2022-12-06 10:00:00", - "2022-12-06 12:00:00", - "2022-12-06 14:00:00", - "2022-12-06 16:00:00", - "2022-12-06 18:00:00", - "2022-12-06 20:00:00", - "2022-12-06 22:00:00", - "2022-12-07 00:00:00", - "2022-12-07 02:00:00", - "2022-12-07 04:00:00", - "2022-12-07 06:00:00", - "2022-12-07 08:00:00", - "2022-12-07 10:00:00", - "2022-12-07 12:00:00", - "2022-12-07 14:00:00", - "2022-12-07 16:00:00", - "2022-12-07 18:00:00", - "2022-12-07 20:00:00", - "2022-12-07 22:00:00", - "2022-12-08 00:00:00", - "2022-12-08 02:00:00", - "2022-12-08 04:00:00", - "2022-12-08 06:00:00", - "2022-12-08 08:00:00", - "2022-12-08 10:00:00", - "2022-12-08 12:00:00", - "2022-12-08 14:00:00", - "2022-12-08 16:00:00", - "2022-12-08 18:00:00", - "2022-12-08 20:00:00", - "2022-12-08 22:00:00", - "2022-12-09 00:00:00", - "2022-12-09 02:00:00", - "2022-12-09 04:00:00", - "2022-12-09 06:00:00", - "2022-12-09 08:00:00", - "2022-12-09 10:00:00", - "2022-12-09 12:00:00", - "2022-12-09 14:00:00", - "2022-12-09 16:00:00", - "2022-12-09 18:00:00", - "2022-12-09 20:00:00", - "2022-12-09 22:00:00", - "2022-12-10 00:00:00", - "2022-12-10 02:00:00", - "2022-12-10 04:00:00", - "2022-12-10 06:00:00", - "2022-12-10 08:00:00", - "2022-12-10 10:00:00", - "2022-12-10 12:00:00", - "2022-12-10 14:00:00", - "2022-12-10 16:00:00", - "2022-12-10 18:00:00", - "2022-12-10 20:00:00", - "2022-12-10 22:00:00", - "2022-12-11 00:00:00", - "2022-12-11 02:00:00", - "2022-12-11 04:00:00", - "2022-12-11 06:00:00", - "2022-12-11 08:00:00", - "2022-12-11 10:00:00", - "2022-12-11 12:00:00", - "2022-12-11 14:00:00", - "2022-12-11 16:00:00", - "2022-12-11 18:00:00", - "2022-12-11 20:00:00", - "2022-12-11 22:00:00", - "2022-12-12 00:00:00", - "2022-12-12 02:00:00", - "2022-12-12 04:00:00", - "2022-12-12 06:00:00", - "2022-12-12 08:00:00", - "2022-12-12 10:00:00", - "2022-12-12 12:00:00", - "2022-12-12 14:00:00", - "2022-12-12 16:00:00", - "2022-12-12 18:00:00", - "2022-12-12 20:00:00", - "2022-12-12 22:00:00", - "2022-12-13 00:00:00", - "2022-12-13 02:00:00", - "2022-12-13 04:00:00", - "2022-12-13 06:00:00", - "2022-12-13 08:00:00", - "2022-12-13 10:00:00", - "2022-12-13 12:00:00", - "2022-12-13 14:00:00", - "2022-12-13 16:00:00", - "2022-12-13 18:00:00", - "2022-12-13 20:00:00", - "2022-12-13 22:00:00", - "2022-12-14 00:00:00", - "2022-12-14 02:00:00", - "2022-12-14 04:00:00", - "2022-12-14 06:00:00", - "2022-12-14 08:00:00", - "2022-12-14 10:00:00", - "2022-12-14 12:00:00", - "2022-12-14 14:00:00", - "2022-12-14 16:00:00", - "2022-12-14 18:00:00", - "2022-12-14 20:00:00", - "2022-12-14 22:00:00", - "2022-12-15 00:00:00", - "2022-12-15 02:00:00", - "2022-12-15 04:00:00", - "2022-12-15 06:00:00", - "2022-12-15 08:00:00", - "2022-12-15 10:00:00", - "2022-12-15 12:00:00", - "2022-12-15 14:00:00", - "2022-12-15 16:00:00", - "2022-12-15 18:00:00", - "2022-12-15 20:00:00", - "2022-12-15 22:00:00", - "2022-12-16 00:00:00", - "2022-12-16 02:00:00", - "2022-12-16 04:00:00", - "2022-12-16 06:00:00", - "2022-12-16 08:00:00", - "2022-12-16 10:00:00", - "2022-12-16 12:00:00", - "2022-12-16 14:00:00", - "2022-12-16 16:00:00", - "2022-12-16 18:00:00", - "2022-12-16 20:00:00", - "2022-12-16 22:00:00", - "2022-12-17 00:00:00", - "2022-12-17 02:00:00", - "2022-12-17 04:00:00", - "2022-12-17 06:00:00", - "2022-12-17 08:00:00", - "2022-12-17 10:00:00", - "2022-12-17 12:00:00", - "2022-12-17 14:00:00", - "2022-12-17 16:00:00", - "2022-12-17 18:00:00", - "2022-12-17 20:00:00", - "2022-12-17 22:00:00", - "2022-12-18 00:00:00", - "2022-12-18 02:00:00", - "2022-12-18 04:00:00", - "2022-12-18 06:00:00", - "2022-12-18 08:00:00", - "2022-12-18 10:00:00", - "2022-12-18 12:00:00", - "2022-12-18 14:00:00", - "2022-12-18 16:00:00", - "2022-12-18 18:00:00", - "2022-12-18 20:00:00", - "2022-12-18 22:00:00", - "2022-12-19 00:00:00", - "2022-12-19 02:00:00", - "2022-12-19 04:00:00", - "2022-12-19 06:00:00", - "2022-12-19 08:00:00", - "2022-12-19 10:00:00", - "2022-12-19 12:00:00", - "2022-12-19 14:00:00", - "2022-12-19 16:00:00", - "2022-12-19 18:00:00", - "2022-12-19 20:00:00", - "2022-12-19 22:00:00", - "2022-12-20 00:00:00", - "2022-12-20 02:00:00", - "2022-12-20 04:00:00", - "2022-12-20 06:00:00", - "2022-12-20 08:00:00", - "2022-12-20 10:00:00", - "2022-12-20 12:00:00", - "2022-12-20 14:00:00", - "2022-12-20 16:00:00", - "2022-12-20 18:00:00", - "2022-12-20 20:00:00", - "2022-12-20 22:00:00", - "2022-12-21 00:00:00", - "2022-12-21 02:00:00", - "2022-12-21 04:00:00", - "2022-12-21 06:00:00", - "2022-12-21 08:00:00", - "2022-12-21 10:00:00", - "2022-12-21 12:00:00", - "2022-12-21 14:00:00", - "2022-12-21 16:00:00", - "2022-12-21 18:00:00", - "2022-12-21 20:00:00", - "2022-12-21 22:00:00", - "2022-12-22 00:00:00", - "2022-12-22 02:00:00", - "2022-12-22 04:00:00", - "2022-12-22 06:00:00", - "2022-12-22 08:00:00", - "2022-12-22 10:00:00", - "2022-12-22 12:00:00", - "2022-12-22 14:00:00", - "2022-12-22 16:00:00", - "2022-12-22 18:00:00", - "2022-12-22 20:00:00", - "2022-12-22 22:00:00", - "2022-12-23 00:00:00", - "2022-12-23 02:00:00", - "2022-12-23 04:00:00", - "2022-12-23 06:00:00", - "2022-12-23 08:00:00", - "2022-12-23 10:00:00", - "2022-12-23 12:00:00", - "2022-12-23 14:00:00", - "2022-12-23 16:00:00", - "2022-12-23 18:00:00", - "2022-12-23 20:00:00", - "2022-12-23 22:00:00", - "2022-12-24 00:00:00", - "2022-12-24 02:00:00", - "2022-12-24 04:00:00", - "2022-12-24 06:00:00", - "2022-12-24 08:00:00", - "2022-12-24 10:00:00", - "2022-12-24 12:00:00", - "2022-12-24 14:00:00", - "2022-12-24 16:00:00", - "2022-12-24 18:00:00", - "2022-12-24 20:00:00", - "2022-12-24 22:00:00", - "2022-12-25 00:00:00", - "2022-12-25 02:00:00", - "2022-12-25 04:00:00", - "2022-12-25 06:00:00", - "2022-12-25 08:00:00", - "2022-12-25 10:00:00", - "2022-12-25 12:00:00", - "2022-12-25 14:00:00", - "2022-12-25 16:00:00", - "2022-12-25 18:00:00", - "2022-12-25 20:00:00", - "2022-12-25 22:00:00", - "2022-12-26 00:00:00", - "2022-12-26 02:00:00", - "2022-12-26 04:00:00", - "2022-12-26 06:00:00", - "2022-12-26 08:00:00", - "2022-12-26 10:00:00", - "2022-12-26 12:00:00", - "2022-12-26 14:00:00", - "2022-12-26 16:00:00", - "2022-12-26 18:00:00", - "2022-12-26 20:00:00", - "2022-12-26 22:00:00", - "2022-12-27 00:00:00", - "2022-12-27 02:00:00", - "2022-12-27 04:00:00", - "2022-12-27 06:00:00", - "2022-12-27 08:00:00", - "2022-12-27 10:00:00", - "2022-12-27 12:00:00", - "2022-12-27 14:00:00", - "2022-12-27 16:00:00", - "2022-12-27 18:00:00", - "2022-12-27 20:00:00", - "2022-12-27 22:00:00", - "2022-12-28 00:00:00", - "2022-12-28 02:00:00", - "2022-12-28 04:00:00", - "2022-12-28 06:00:00", - "2022-12-28 08:00:00", - "2022-12-28 10:00:00", - "2022-12-28 12:00:00", - "2022-12-28 14:00:00", - "2022-12-28 16:00:00", - "2022-12-28 18:00:00", - "2022-12-28 20:00:00", - "2022-12-28 22:00:00", - "2022-12-29 00:00:00", - "2022-12-29 02:00:00", - "2022-12-29 04:00:00", - "2022-12-29 06:00:00", - "2022-12-29 08:00:00", - "2022-12-29 10:00:00", - "2022-12-29 12:00:00", - "2022-12-29 14:00:00", - "2022-12-29 16:00:00", - "2022-12-29 18:00:00", - "2022-12-29 20:00:00", - "2022-12-29 22:00:00", - "2022-12-30 00:00:00", - "2022-12-30 02:00:00", - "2022-12-30 04:00:00", - "2022-12-30 06:00:00", - "2022-12-30 08:00:00", - "2022-12-30 10:00:00", - "2022-12-30 12:00:00", - "2022-12-30 14:00:00", - "2022-12-30 16:00:00", - "2022-12-30 18:00:00", - "2022-12-30 20:00:00", - "2022-12-30 22:00:00", - "2022-12-31 00:00:00", - "2022-12-31 02:00:00", - "2022-12-31 04:00:00", - "2022-12-31 06:00:00", - "2022-12-31 08:00:00", - "2022-12-31 10:00:00", - "2022-12-31 12:00:00", - "2022-12-31 14:00:00", - "2022-12-31 16:00:00", - "2022-12-31 18:00:00", - "2022-12-31 20:00:00", - "2022-12-31 22:00:00", - "2023-01-01 00:00:00", - "2023-01-01 02:00:00", - "2023-01-01 04:00:00", - "2023-01-01 06:00:00", - "2023-01-01 08:00:00", - "2023-01-01 10:00:00", - "2023-01-01 12:00:00", - "2023-01-01 14:00:00", - "2023-01-01 16:00:00", - "2023-01-01 18:00:00", - "2023-01-01 20:00:00", - "2023-01-01 22:00:00", - "2023-01-02 00:00:00", - "2023-01-02 02:00:00", - "2023-01-02 04:00:00", - "2023-01-02 06:00:00", - "2023-01-02 08:00:00", - "2023-01-02 10:00:00", - "2023-01-02 12:00:00", - "2023-01-02 14:00:00", - "2023-01-02 16:00:00", - "2023-01-02 18:00:00", - "2023-01-02 20:00:00", - "2023-01-02 22:00:00", - "2023-01-03 00:00:00", - "2023-01-03 02:00:00", - "2023-01-03 04:00:00", - "2023-01-03 06:00:00", - "2023-01-03 08:00:00", - "2023-01-03 10:00:00", - "2023-01-03 12:00:00", - "2023-01-03 14:00:00", - "2023-01-03 16:00:00", - "2023-01-03 18:00:00", - "2023-01-03 20:00:00", - "2023-01-03 22:00:00", - "2023-01-04 00:00:00", - "2023-01-04 02:00:00", - "2023-01-04 04:00:00", - "2023-01-04 06:00:00", - "2023-01-04 08:00:00", - "2023-01-04 10:00:00", - "2023-01-04 12:00:00", - "2023-01-04 14:00:00", - "2023-01-04 16:00:00", - "2023-01-04 18:00:00", - "2023-01-04 20:00:00", - "2023-01-04 22:00:00", - "2023-01-05 00:00:00", - "2023-01-05 02:00:00", - "2023-01-05 04:00:00", - "2023-01-05 06:00:00", - "2023-01-05 08:00:00", - "2023-01-05 10:00:00", - "2023-01-05 12:00:00", - "2023-01-05 14:00:00", - "2023-01-05 16:00:00", - "2023-01-05 18:00:00", - "2023-01-05 20:00:00", - "2023-01-05 22:00:00", - "2023-01-06 00:00:00", - "2023-01-06 02:00:00", - "2023-01-06 04:00:00", - "2023-01-06 06:00:00", - "2023-01-06 08:00:00", - "2023-01-06 10:00:00", - "2023-01-06 12:00:00", - "2023-01-06 14:00:00", - "2023-01-06 16:00:00", - "2023-01-06 18:00:00", - "2023-01-06 20:00:00", - "2023-01-06 22:00:00", - "2023-01-07 00:00:00", - "2023-01-07 02:00:00", - "2023-01-07 04:00:00", - "2023-01-07 06:00:00", - "2023-01-07 08:00:00", - "2023-01-07 10:00:00", - "2023-01-07 12:00:00", - "2023-01-07 14:00:00", - "2023-01-07 16:00:00", - "2023-01-07 18:00:00", - "2023-01-07 20:00:00", - "2023-01-07 22:00:00", - "2023-01-08 00:00:00", - "2023-01-08 02:00:00", - "2023-01-08 04:00:00", - "2023-01-08 06:00:00", - "2023-01-08 08:00:00", - "2023-01-08 10:00:00", - "2023-01-08 12:00:00", - "2023-01-08 14:00:00", - "2023-01-08 16:00:00", - "2023-01-08 18:00:00", - "2023-01-08 20:00:00", - "2023-01-08 22:00:00", - "2023-01-09 00:00:00", - "2023-01-09 02:00:00", - "2023-01-09 04:00:00", - "2023-01-09 06:00:00", - "2023-01-09 08:00:00", - "2023-01-09 10:00:00", - "2023-01-09 12:00:00", - "2023-01-09 14:00:00", - "2023-01-09 16:00:00", - "2023-01-09 18:00:00", - "2023-01-09 20:00:00", - "2023-01-09 22:00:00", - "2023-01-10 00:00:00" - ], - "y": [ - 28304.0, - 28309.0, - 28268.0, - 28190.0, - 28370.0, - 27909.0, - 27989.0, - 27664.0, - 27527.0, - 27745.0, - 27666.0, - 27751.0, - 27816.0, - 27854.0, - 27895.0, - 27642.0, - 27334.0, - 27381.0, - 27026.0, - 27114.0, - 27209.0, - 27141.0, - 27012.0, - 26810.0, - 26235.0, - 25957.0, - 26317.0, - 26155.0, - 26117.0, - 25984.0, - 26725.0, - 26750.0, - 26040.0, - 26007.0, - 25305.0, - 24576.0, - 24286.0, - 24034.0, - 23681.0, - 22947.0, - 22676.0, - 22646.0, - 22310.0, - 22526.0, - 22270.0, - 22162.0, - 21584.0, - 20091.0, - 21217.0, - 21811.0, - 21855.0, - 21361.0, - 21037.0, - 21473.0, - 21784.0, - 21455.0, - 21325.0, - 20624.0, - 21232.0, - 21211.0, - 20512.0, - 20264.0, - 19795.0, - 19254.0, - 20227.0, - 20638.0, - 20581.0, - 20275.0, - 20686.0, - 21241.0, - 21582.0, - 21426.0, - 21230.0, - 21033.0, - 20938.0, - 20410.0, - 20261.0, - 20111.0, - 19988.0, - 19890.0, - 19765.0, - 19552.0, - 19331.0, - 19796.0, - 19339.0, - 19760.0, - 20052.0, - 19910.0, - 19987.0, - 19811.0, - 19733.0, - 19589.0, - 19525.0, - 19566.0, - 19466.0, - 19442.0, - 19480.0, - 19445.0, - 18364.0, - 18275.0, - 18336.0, - 18292.0, - 18039.0, - 17710.0, - 17188.0, - 17614.0, - 18068.0, - 17558.0, - 17419.0, - 17264.0, - 17431.0, - 18098.0, - 18714.0, - 18280.0, - 18856.0, - 18523.0, - 19162.0, - 19450.0, - 19608.0, - 18851.0, - 18953.0, - 19047.0, - 19075.0, - 19577.0, - 19745.0, - 19489.0, - 19753.0, - 18979.0, - 19097.0, - 19398.0, - 19531.0, - 19583.0, - 19538.0, - 19837.0, - 19995.0, - 20042.0, - 19834.0, - 20475.0, - 20267.0, - 20131.0, - 19827.0, - 19916.0, - 19671.0, - 19415.0, - 19295.0, - 19399.0, - 19126.0, - 19416.0, - 19479.0, - 19637.0, - 18963.0, - 19158.0, - 19042.0, - 18921.0, - 18884.0, - 19288.0, - 19120.0, - 19248.0, - 19481.0, - 19737.0, - 19635.0, - 19389.0, - 19364.0, - 19331.0, - 19853.0, - 19639.0, - 20052.0, - 19962.0, - 19996.0, - 19816.0, - 19870.0, - 19727.0, - 20102.0, - 20109.0, - 19810.0, - 19879.0, - 20118.0, - 20184.0, - 20090.0, - 20182.0, - 20190.0, - 20100.0, - 20386.0, - 20332.0, - 20232.0, - 20063.0, - 19879.0, - 20005.0, - 20091.0, - 20365.0, - 20354.0, - 20279.0, - 20294.0, - 20322.0, - 20278.0, - 20312.0, - 20572.0, - 20305.0, - 20232.0, - 20151.0, - 20181.0, - 20243.0, - 19923.0, - 19962.0, - 20069.0, - 20050.0, - 20149.0, - 20288.0, - 20143.0, - 19599.0, - 19575.0, - 19571.0, - 19709.0, - 19623.0, - 19585.0, - 19476.0, - 19586.0, - 19611.0, - 19729.0, - 19882.0, - 19841.0, - 19980.0, - 19530.0, - 19569.0, - 19236.0, - 19367.0, - 19263.0, - 19324.0, - 19257.0, - 19164.0, - 19082.0, - 19070.0, - 19066.0, - 19143.0, - 19147.0, - 19173.0, - 19334.0, - 19406.0, - 19241.0, - 19158.0, - 19174.0, - 19115.0, - 18594.0, - 18357.0, - 18363.0, - 18107.0, - 18280.0, - 18230.0, - 18039.0, - 17949.0, - 19020.0, - 19466.0, - 18837.0, - 18562.0, - 18725.0, - 18555.0, - 18377.0, - 18722.0, - 18509.0, - 18708.0, - 18565.0, - 18564.0, - 18470.0, - 18435.0, - 18345.0, - 18444.0, - 18339.0, - 18388.0, - 18393.0, - 18466.0, - 18440.0, - 18488.0, - 18478.0, - 18535.0, - 18431.0, - 18396.0, - 18462.0, - 18247.0, - 18341.0, - 18257.0, - 18307.0, - 18191.0, - 18266.0, - 18405.0, - 18693.0, - 18508.0, - 18496.0, - 18380.0, - 18317.0, - 18333.0, - 18314.0, - 18530.0, - 18922.0, - 18668.0, - 18956.0, - 19054.0, - 19004.0, - 19107.0, - 19350.0, - 19407.0, - 19444.0, - 19391.0, - 19445.0, - 19179.0, - 18919.0, - 18888.0, - 19011.0, - 19192.0, - 19825.0, - 19807.0, - 19633.0, - 19342.0, - 19419.0, - 19534.0, - 19630.0, - 19733.0, - 19696.0, - 19857.0, - 19787.0, - 19893.0, - 19923.0, - 19966.0, - 20171.0, - 20129.0, - 20058.0, - 19831.0, - 20045.0, - 20118.0, - 20101.0, - 20133.0, - 20527.0, - 20549.0, - 21473.0, - 21313.0, - 21260.0, - 21709.0, - 21786.0, - 21494.0, - 21548.0, - 21289.0, - 21241.0, - 20951.0, - 21552.0, - 21370.0, - 21400.0, - 21320.0, - 21197.0, - 21120.0, - 21223.0, - 21143.0, - 21247.0, - 21267.0, - 21065.0, - 21189.0, - 21170.0, - 21491.0, - 21238.0, - 21326.0, - 21200.0, - 21014.0, - 20902.0, - 20984.0, - 20963.0, - 20929.0, - 20904.0, - 20690.0, - 20520.0, - 20524.0, - 20545.0, - 20660.0, - 20499.0, - 20252.0, - 20237.0, - 20209.0, - 20200.0, - 20329.0, - 20268.0, - 20230.0, - 20254.0, - 20458.0, - 20381.0, - 20135.0, - 19867.0, - 19808.0, - 19884.0, - 19916.0, - 19702.0, - 19559.0, - 19691.0, - 19659.0, - 19742.0, - 19759.0, - 19340.0, - 19274.0, - 19239.0, - 19365.0, - 19397.0, - 19444.0, - 19640.0, - 19722.0, - 19713.0, - 19222.0, - 19294.0, - 19721.0, - 19532.0, - 19810.0, - 20171.0, - 20215.0, - 20125.0, - 20044.0, - 19905.0, - 19732.0, - 19719.0, - 19815.0, - 20304.0, - 20682.0, - 20550.0, - 20402.0, - 20506.0, - 20533.0, - 20528.0, - 20600.0, - 20749.0, - 20809.0, - 20635.0, - 20773.0, - 20611.0, - 20616.0, - 20979.0, - 20790.0, - 20633.0, - 20539.0, - 20557.0, - 20399.0, - 20383.0, - 20444.0, - 20443.0, - 20688.0, - 20780.0, - 21038.0, - 20982.0, - 21012.0, - 21040.0, - 20948.0, - 21146.0, - 21157.0, - 21282.0, - 21181.0, - 21268.0, - 20953.0, - 21000.0, - 20731.0, - 20856.0, - 20836.0, - 20581.0, - 20745.0, - 21076.0, - 21647.0, - 21912.0, - 21893.0, - 21812.0, - 21780.0, - 22257.0, - 21531.0, - 21301.0, - 21355.0, - 22145.0, - 21788.0, - 21623.0, - 21688.0, - 21249.0, - 21420.0, - 21452.0, - 21789.0, - 22613.0, - 22942.0, - 22924.0, - 23113.0, - 22889.0, - 22731.0, - 22881.0, - 22947.0, - 22864.0, - 22969.0, - 23287.0, - 23280.0, - 23689.0, - 23152.0, - 23261.0, - 23211.0, - 22828.0, - 22786.0, - 22310.0, - 22454.0, - 22477.0, - 22602.0, - 22168.0, - 22252.0, - 22200.0, - 22423.0, - 22713.0, - 22700.0, - 22632.0, - 22661.0, - 22494.0, - 22728.0, - 22807.0, - 23101.0, - 23204.0, - 22921.0, - 22793.0, - 22504.0, - 22136.0, - 22272.0, - 22213.0, - 22313.0, - 22381.0, - 22424.0, - 22304.0, - 22229.0, - 21785.0, - 21912.0, - 21766.0, - 21604.0, - 21844.0, - 21818.0, - 21997.0, - 21945.0, - 21881.0, - 22299.0, - 22250.0, - 22172.0, - 22209.0, - 22101.0, - 22372.0, - 22319.0, - 22238.0, - 22357.0, - 22141.0, - 21797.0, - 21431.0, - 21514.0, - 21569.0, - 21492.0, - 21427.0, - 21400.0, - 21466.0, - 21418.0, - 21424.0, - 21565.0, - 20836.0, - 20588.0, - 20683.0, - 20716.0, - 20624.0, - 20759.0, - 20805.0, - 20669.0, - 20500.0, - 20694.0, - 20692.0, - 20702.0, - 21000.0, - 20869.0, - 20784.0, - 20941.0, - 21070.0, - 20981.0, - 20872.0, - 21178.0, - 21314.0, - 21354.0, - 22290.0, - 22260.0, - 22501.0, - 22319.0, - 22637.0, - 22752.0, - 22443.0, - 22634.0, - 22837.0, - 22528.0, - 23341.0, - 23638.0, - 23349.0, - 23448.0, - 23412.0, - 23418.0, - 23424.0, - 23513.0, - 23401.0, - 23609.0, - 23228.0, - 23332.0, - 23539.0, - 23299.0, - 23375.0, - 23287.0, - 23267.0, - 23384.0, - 23299.0, - 23269.0, - 23364.0, - 23411.0, - 23460.0, - 23956.0, - 23985.0, - 23963.0, - 23421.0, - 23372.0, - 23133.0, - 23227.0, - 23262.0, - 23216.0, - 23233.0, - 23288.0, - 23300.0, - 23250.0, - 23270.0, - 23245.0, - 23347.0, - 23101.0, - 22862.0, - 22908.0, - 22866.0, - 22854.0, - 22814.0, - 22724.0, - 22717.0, - 22672.0, - 22748.0, - 22399.0, - 22395.0, - 22455.0, - 22683.0, - 22408.0, - 22243.0, - 22282.0, - 22418.0, - 22180.0, - 22388.0, - 22353.0, - 22550.0, - 22782.0, - 22613.0, - 22710.0, - 22645.0, - 22440.0, - 22444.0, - 22535.0, - 22591.0, - 22969.0, - 23022.0, - 22951.0, - 23026.0, - 23103.0, - 23066.0, - 22913.0, - 22508.0, - 22768.0, - 22724.0, - 22769.0, - 22579.0, - 22441.0, - 22515.0, - 22630.0, - 22440.0, - 22125.0, - 21937.0, - 21951.0, - 22091.0, - 22390.0, - 22628.0, - 22662.0, - 22665.0, - 22614.0, - 22863.0, - 22670.0, - 22625.0, - 22439.0, - 22526.0, - 22686.0, - 22878.0, - 22787.0, - 22740.0, - 22782.0, - 22797.0, - 22770.0, - 22756.0, - 22792.0, - 22785.0, - 22721.0, - 22796.0, - 22781.0, - 22540.0, - 22508.0, - 22616.0, - 22562.0, - 22623.0, - 22590.0, - 22642.0, - 22768.0, - 22618.0, - 22832.0, - 22759.0, - 22924.0, - 22807.0, - 22818.0, - 22891.0, - 23017.0, - 23335.0, - 23643.0, - 23674.0, - 23700.0, - 23515.0, - 23485.0, - 23496.0, - 23410.0, - 23331.0, - 23303.0, - 23390.0, - 23416.0, - 23393.0, - 22951.0, - 22791.0, - 22555.0, - 22617.0, - 22593.0, - 22617.0, - 22733.0, - 22685.0, - 22432.0, - 22395.0, - 22428.0, - 22490.0, - 22586.0, - 22556.0, - 23265.0, - 23214.0, - 23162.0, - 22928.0, - 23260.0, - 23229.0, - 23728.0, - 23647.0, - 23874.0, - 23656.0, - 23668.0, - 23804.0, - 23868.0, - 23564.0, - 23578.0, - 23442.0, - 23447.0, - 23215.0, - 23229.0, - 23307.0, - 23232.0, - 23282.0, - 23314.0, - 23052.0, - 23197.0, - 23403.0, - 23417.0, - 23588.0, - 23526.0, - 23790.0, - 23926.0, - 24149.0, - 24063.0, - 23915.0, - 23970.0, - 23805.0, - 23856.0, - 23899.0, - 23964.0, - 23812.0, - 23912.0, - 23840.0, - 23966.0, - 23967.0, - 23992.0, - 24256.0, - 24157.0, - 23956.0, - 23901.0, - 23944.0, - 23650.0, - 23692.0, - 23755.0, - 23709.0, - 24022.0, - 24253.0, - 24254.0, - 23500.0, - 23727.0, - 23617.0, - 23718.0, - 23802.0, - 23746.0, - 23604.0, - 23648.0, - 23732.0, - 23768.0, - 23722.0, - 23569.0, - 23686.0, - 23779.0, - 23729.0, - 23507.0, - 23491.0, - 23463.0, - 23518.0, - 23570.0, - 23466.0, - 23527.0, - 23584.0, - 23752.0, - 23406.0, - 23412.0, - 23347.0, - 23052.0, - 23035.0, - 23098.0, - 22856.0, - 22938.0, - 22940.0, - 23055.0, - 23041.0, - 23012.0, - 23042.0, - 23144.0, - 23118.0, - 23165.0, - 23199.0, - 23091.0, - 23155.0, - 23162.0, - 22981.0, - 22600.0, - 22610.0, - 22633.0, - 21729.0, - 21570.0, - 21371.0, - 21272.0, - 21375.0, - 21385.0, - 21264.0, - 21000.0, - 20729.0, - 20899.0, - 21054.0, - 21057.0, - 21249.0, - 21140.0, - 21192.0, - 21246.0, - 21245.0, - 21190.0, - 20890.0, - 21046.0, - 21058.0, - 21175.0, - 21118.0, - 21143.0, - 21161.0, - 21383.0, - 21460.0, - 21326.0, - 21349.0, - 21401.0, - 21432.0, - 21445.0, - 21451.0, - 21394.0, - 21331.0, - 21369.0, - 21098.0, - 21150.0, - 21253.0, - 21362.0, - 21548.0, - 21392.0, - 21177.0, - 21216.0, - 21521.0, - 21430.0, - 21360.0, - 21138.0, - 21454.0, - 21589.0, - 21629.0, - 21555.0, - 21533.0, - 21515.0, - 21600.0, - 21539.0, - 21581.0, - 21364.0, - 21371.0, - 21534.0, - 21420.0, - 21425.0, - 21607.0, - 21610.0, - 21687.0, - 21714.0, - 21771.0, - 21657.0, - 21429.0, - 21614.0, - 21504.0, - 21584.0, - 21743.0, - 21700.0, - 21775.0, - 21791.0, - 21668.0, - 21635.0, - 21661.0, - 21611.0, - 21639.0, - 21609.0, - 21628.0, - 21500.0, - 21500.0, - 21410.0, - 21191.0, - 21624.0, - 20791.0, - 20704.0, - 20727.0, - 20733.0, - 20342.0, - 20383.0, - 20321.0, - 20238.0, - 20384.0, - 20254.0, - 20315.0, - 20276.0, - 20063.0, - 20083.0, - 20117.0, - 19975.0, - 20108.0, - 20105.0, - 20086.0, - 20110.0, - 20140.0, - 20104.0, - 20133.0, - 20039.0, - 20124.0, - 20066.0, - 20069.0, - 20042.0, - 19685.0, - 19809.0, - 20028.0, - 19908.0, - 20013.0, - 19905.0, - 19821.0, - 20055.0, - 20274.0, - 20282.0, - 20090.0, - 20221.0, - 20268.0, - 20194.0, - 20272.0, - 20419.0, - 20379.0, - 20346.0, - 20368.0, - 20226.0, - 19744.0, - 19669.0, - 19907.0, - 19852.0, - 19754.0, - 20187.0, - 20334.0, - 20254.0, - 20244.0, - 20198.0, - 20308.0, - 20303.0, - 20039.0, - 19898.0, - 20124.0, - 20167.0, - 19986.0, - 20064.0, - 19972.0, - 20023.0, - 19828.0, - 19883.0, - 20080.0, - 19973.0, - 19890.0, - 19804.0, - 19959.0, - 20178.0, - 20197.0, - 20095.0, - 20223.0, - 20196.0, - 20117.0, - 20112.0, - 20114.0, - 20254.0, - 20286.0, - 19872.0, - 20010.0, - 20096.0, - 20068.0, - 20061.0, - 20001.0, - 20057.0, - 19896.0, - 19906.0, - 19889.0, - 19960.0, - 19888.0, - 19931.0, - 19803.0, - 19867.0, - 19943.0, - 19896.0, - 19876.0, - 19885.0, - 19770.0, - 19893.0, - 19929.0, - 19818.0, - 19903.0, - 19925.0, - 19988.0, - 20001.0, - 20170.0, - 19994.0, - 20049.0, - 19952.0, - 19934.0, - 19930.0, - 19873.0, - 19884.0, - 19975.0, - 19946.0, - 19835.0, - 19896.0, - 19875.0, - 19819.0, - 19862.0, - 19887.0, - 19966.0, - 20001.0, - 20060.0, - 19973.0, - 20008.0, - 19439.0, - 19023.0, - 19175.0, - 19002.0, - 19002.0, - 18982.0, - 18976.0, - 18968.0, - 18966.0, - 18990.0, - 19043.0, - 19007.0, - 19030.0, - 19000.0, - 19340.0, - 19293.0, - 19210.0, - 19224.0, - 19314.0, - 19206.0, - 19190.0, - 19264.0, - 19287.0, - 19356.0, - 19203.0, - 19365.0, - 19347.0, - 19284.0, - 19250.0, - 19798.0, - 20365.0, - 20426.0, - 20562.0, - 20845.0, - 20898.0, - 21154.0, - 21146.0, - 21164.0, - 21126.0, - 21234.0, - 21157.0, - 21161.0, - 21418.0, - 21177.0, - 21195.0, - 21155.0, - 21050.0, - 21088.0, - 21179.0, - 21450.0, - 21479.0, - 21530.0, - 21486.0, - 21416.0, - 21494.0, - 21500.0, - 21470.0, - 21486.0, - 21411.0, - 21505.0, - 21521.0, - 21471.0, - 21284.0, - 21680.0, - 21825.0, - 21460.0, - 21612.0, - 21799.0, - 21834.0, - 21995.0, - 22134.0, - 21825.0, - 22046.0, - 22161.0, - 22058.0, - 22109.0, - 21947.0, - 21956.0, - 22078.0, - 21955.0, - 22011.0, - 22145.0, - 21090.0, - 20793.0, - 20809.0, - 20358.0, - 20235.0, - 20246.0, - 20356.0, - 20358.0, - 20351.0, - 20269.0, - 20334.0, - 20245.0, - 20343.0, - 20201.0, - 20139.0, - 20014.0, - 20281.0, - 20248.0, - 20244.0, - 20071.0, - 20235.0, - 20245.0, - 20156.0, - 20186.0, - 20125.0, - 19746.0, - 19828.0, - 19809.0, - 19842.0, - 19746.0, - 19734.0, - 19760.0, - 19789.0, - 19824.0, - 19817.0, - 19936.0, - 19581.0, - 19651.0, - 19453.0, - 19610.0, - 19720.0, - 19776.0, - 19975.0, - 19865.0, - 19821.0, - 19809.0, - 19859.0, - 19793.0, - 19873.0, - 19941.0, - 20072.0, - 20004.0, - 20015.0, - 20099.0, - 19968.0, - 19954.0, - 20038.0, - 20037.0, - 19815.0, - 19898.0, - 19931.0, - 19782.0, - 19600.0, - 19696.0, - 19459.0, - 19384.0, - 19434.0, - 18773.0, - 18524.0, - 18501.0, - 18525.0, - 18716.0, - 19149.0, - 19101.0, - 18963.0, - 19476.0, - 19460.0, - 19474.0, - 19429.0, - 19315.0, - 19331.0, - 19293.0, - 19199.0, - 19246.0, - 18942.0, - 19274.0, - 18851.0, - 19033.0, - 18948.0, - 18940.0, - 18960.0, - 19089.0, - 19029.0, - 19110.0, - 19123.0, - 19345.0, - 19464.0, - 19418.0, - 19851.0, - 19279.0, - 18775.0, - 18845.0, - 18861.0, - 19035.0, - 19010.0, - 19226.0, - 19446.0, - 19479.0, - 19222.0, - 19308.0, - 19364.0, - 19609.0, - 19496.0, - 19719.0, - 19657.0, - 19714.0, - 19720.0, - 19650.0, - 19553.0, - 19325.0, - 19142.0, - 19267.0, - 19369.0, - 19407.0, - 19593.0, - 19885.0, - 19702.0, - 19726.0, - 19738.0, - 19595.0, - 19622.0, - 19650.0, - 19708.0, - 19715.0, - 19682.0, - 19707.0, - 19549.0, - 19521.0, - 19569.0, - 19650.0, - 19590.0, - 19699.0, - 19641.0, - 19708.0, - 19522.0, - 19572.0, - 19579.0, - 19520.0, - 19383.0, - 19407.0, - 19539.0, - 19591.0, - 19520.0, - 19541.0, - 19769.0, - 19570.0, - 19838.0, - 19863.0, - 19892.0, - 19972.0, - 19868.0, - 19972.0, - 20520.0, - 20847.0, - 20916.0, - 20899.0, - 20980.0, - 20986.0, - 20968.0, - 20630.0, - 19784.0, - 19885.0, - 19782.0, - 19887.0, - 19880.0, - 19611.0, - 19680.0, - 19605.0, - 19535.0, - 19785.0, - 19909.0, - 20147.0, - 19886.0, - 20065.0, - 20145.0, - 19987.0, - 20111.0, - 20133.0, - 20118.0, - 20043.0, - 20104.0, - 20005.0, - 19498.0, - 19756.0, - 19700.0, - 19807.0, - 19797.0, - 19942.0, - 19879.0, - 19791.0, - 19801.0, - 19906.0, - 19927.0, - 19925.0, - 19914.0, - 20191.0, - 20104.0, - 19900.0, - 19760.0, - 19818.0, - 19819.0, - 19805.0, - 19704.0, - 19712.0, - 19725.0, - 19707.0, - 19737.0, - 19737.0, - 19681.0, - 19667.0, - 19694.0, - 19713.0, - 19704.0, - 19713.0, - 19730.0, - 19691.0, - 19680.0, - 19615.0, - 19570.0, - 19546.0, - 19589.0, - 19683.0, - 19593.0, - 19448.0, - 19580.0, - 19547.0, - 19617.0, - 19561.0, - 19635.0, - 19700.0, - 19639.0, - 19713.0, - 19950.0, - 19892.0, - 19913.0, - 19970.0, - 19902.0, - 19912.0, - 19938.0, - 20224.0, - 20183.0, - 20159.0, - 20245.0, - 20137.0, - 20095.0, - 20262.0, - 20318.0, - 20387.0, - 20294.0, - 20254.0, - 20268.0, - 20366.0, - 20242.0, - 20226.0, - 20177.0, - 20277.0, - 20417.0, - 20352.0, - 20296.0, - 20350.0, - 20546.0, - 20526.0, - 20463.0, - 20413.0, - 20316.0, - 20511.0, - 20458.0, - 20407.0, - 20483.0, - 20441.0, - 20335.0, - 20405.0, - 20424.0, - 20392.0, - 20392.0, - 20331.0, - 20389.0, - 20452.0, - 20110.0, - 20013.0, - 19900.0, - 19997.0, - 20076.0, - 20071.0, - 20136.0, - 20040.0, - 20027.0, - 20033.0, - 20060.0, - 20016.0, - 20012.0, - 20034.0, - 19997.0, - 19855.0, - 19951.0, - 19918.0, - 19907.0, - 19949.0, - 19949.0, - 19942.0, - 20002.0, - 20001.0, - 20067.0, - 20004.0, - 20000.0, - 20003.0, - 19968.0, - 20014.0, - 19993.0, - 19992.0, - 20010.0, - 19909.0, - 19936.0, - 19919.0, - 19878.0, - 19912.0, - 19817.0, - 19846.0, - 19716.0, - 19621.0, - 19666.0, - 19641.0, - 19660.0, - 19660.0, - 19706.0, - 19582.0, - 19623.0, - 19626.0, - 19581.0, - 19587.0, - 19640.0, - 19658.0, - 19654.0, - 19679.0, - 19705.0, - 19764.0, - 19685.0, - 19714.0, - 19736.0, - 19685.0, - 19745.0, - 19762.0, - 19722.0, - 19702.0, - 19665.0, - 19684.0, - 19598.0, - 19541.0, - 19267.0, - 19031.0, - 19488.0, - 19578.0, - 19811.0, - 19844.0, - 19847.0, - 20258.0, - 20243.0, - 20242.0, - 20090.0, - 20183.0, - 20160.0, - 20205.0, - 19870.0, - 19913.0, - 19710.0, - 19658.0, - 19731.0, - 19735.0, - 19756.0, - 19702.0, - 19728.0, - 19668.0, - 19726.0, - 19721.0, - 19683.0, - 19660.0, - 19661.0, - 19630.0, - 19625.0, - 19709.0, - 19674.0, - 19698.0, - 19699.0, - 19705.0, - 19697.0, - 19686.0, - 19690.0, - 19684.0, - 19907.0, - 19692.0, - 19780.0, - 19682.0, - 19699.0, - 19759.0, - 19801.0, - 19820.0, - 19973.0, - 20025.0, - 19852.0, - 19840.0, - 19837.0, - 19828.0, - 19846.0, - 19855.0, - 19859.0, - 19846.0, - 19955.0, - 19841.0, - 19886.0, - 19827.0, - 19679.0, - 19692.0, - 19499.0, - 19562.0, - 19602.0, - 19579.0, - 19590.0, - 19572.0, - 19530.0, - 19621.0, - 19616.0, - 19541.0, - 19696.0, - 19636.0, - 19670.0, - 19628.0, - 19582.0, - 19517.0, - 19510.0, - 19569.0, - 19550.0, - 19605.0, - 19570.0, - 19594.0, - 19592.0, - 19503.0, - 19492.0, - 19480.0, - 19482.0, - 19506.0, - 19496.0, - 19481.0, - 19432.0, - 19514.0, - 19431.0, - 19465.0, - 19464.0, - 19486.0, - 19461.0, - 19427.0, - 19442.0, - 19434.0, - 19436.0, - 19417.0, - 19432.0, - 19431.0, - 19460.0, - 19474.0, - 19519.0, - 19447.0, - 19468.0, - 19490.0, - 19478.0, - 19443.0, - 19479.0, - 19460.0, - 19434.0, - 19469.0, - 19439.0, - 19447.0, - 19473.0, - 19735.0, - 19766.0, - 19774.0, - 19840.0, - 19745.0, - 19696.0, - 19635.0, - 19641.0, - 19718.0, - 19749.0, - 19600.0, - 19565.0, - 19531.0, - 19576.0, - 19601.0, - 19559.0, - 19511.0, - 19566.0, - 19584.0, - 19581.0, - 19537.0, - 19564.0, - 19552.0, - 19823.0, - 20185.0, - 20305.0, - 20290.0, - 20161.0, - 20360.0, - 20354.0, - 20265.0, - 20279.0, - 20534.0, - 20557.0, - 20514.0, - 20697.0, - 20660.0, - 20594.0, - 20567.0, - 20571.0, - 20583.0, - 20616.0, - 20633.0, - 20574.0, - 20529.0, - 20510.0, - 20523.0, - 20602.0, - 20635.0, - 20670.0, - 20481.0, - 20349.0, - 20326.0, - 20311.0, - 20330.0, - 20177.0, - 20281.0, - 20264.0, - 20539.0, - 20619.0, - 20758.0, - 20676.0, - 20655.0, - 20672.0, - 20694.0, - 20819.0, - 20730.0, - 20836.0, - 21015.0, - 20754.0, - 20850.0, - 20981.0, - 20886.0, - 20902.0, - 20920.0, - 20869.0, - 20849.0, - 20840.0, - 20873.0, - 20926.0, - 20774.0, - 20849.0, - 20728.0, - 20738.0, - 20734.0, - 20715.0, - 20785.0, - 20723.0, - 20625.0, - 20599.0, - 20651.0, - 20694.0, - 20813.0, - 20846.0, - 20579.0, - 20641.0, - 20620.0, - 20601.0, - 20633.0, - 20724.0, - 20727.0, - 20693.0, - 20692.0, - 20726.0, - 20731.0, - 20645.0, - 20562.0, - 20714.0, - 20677.0, - 20706.0, - 20692.0, - 20741.0, - 20744.0, - 20746.0, - 20714.0, - 20657.0, - 20669.0, - 20652.0, - 20604.0, - 20660.0, - 20687.0, - 20623.0, - 20525.0, - 20542.0, - 20667.0, - 20669.0, - 20745.0, - 20760.0, - 20830.0, - 20662.0, - 20625.0, - 20735.0, - 20763.0, - 20774.0, - 20775.0, - 20716.0, - 20770.0, - 20803.0, - 20910.0, - 21091.0, - 21044.0, - 20984.0, - 21034.0, - 21079.0, - 20978.0, - 21185.0, - 21265.0, - 21251.0, - 21447.0, - 21518.0, - 21476.0, - 21466.0, - 21366.0, - 21482.0, - 21371.0, - 21393.0, - 21407.0, - 21419.0, - 21419.0, - 21381.0, - 21304.0, - 21332.0, - 21241.0, - 21281.0, - 21332.0, - 21344.0, - 21330.0, - 21340.0, - 21342.0, - 21350.0, - 21313.0, - 21069.0, - 21128.0, - 21019.0, - 20990.0, - 20792.0, - 20735.0, - 20788.0, - 20760.0, - 20780.0, - 20676.0, - 20788.0, - 20653.0, - 20555.0, - 20615.0, - 20142.0, - 19743.0, - 19837.0, - 19789.0, - 19731.0, - 19574.0, - 19404.0, - 19201.0, - 18235.0, - 18745.0, - 18445.0, - 18226.0, - 18215.0, - 18281.0, - 18168.0, - 17688.0, - 17758.0, - 17603.0, - 17099.0, - 16884.0, - 16814.0, - 15744.0, - 15890.0, - 16121.0, - 16321.0, - 16676.0, - 16753.0, - 16725.0, - 16342.0, - 17346.0, - 17234.0, - 16987.0, - 17143.0, - 17462.0, - 17255.0, - 16955.0, - 16719.0, - 16961.0, - 16968.0, - 16909.0, - 16843.0, - 16760.0, - 16307.0, - 16339.0, - 16216.0, - 16198.0, - 16428.0, - 16289.0, - 16309.0, - 16112.0, - 16212.0, - 16273.0, - 16294.0, - 16250.0, - 16300.0, - 16282.0, - 16269.0, - 16202.0, - 16196.0, - 16277.0, - 16300.0, - 16255.0, - 16165.0, - 15940.0, - 16038.0, - 16039.0, - 15932.0, - 15929.0, - 15939.0, - 15808.0, - 15797.0, - 15576.0, - 15613.0, - 15356.0, - 16282.0, - 16206.0, - 16285.0, - 16225.0, - 16052.0, - 16027.0, - 15691.0, - 15886.0, - 16063.0, - 16173.0, - 16279.0, - 16108.0, - 16188.0, - 16116.0, - 16094.0, - 16293.0, - 16382.0, - 16360.0, - 16255.0, - 16325.0, - 16314.0, - 16300.0, - 16382.0, - 16318.0, - 16170.0, - 16049.0, - 16039.0, - 15858.0, - 15785.0, - 15964.0, - 15931.0, - 15924.0, - 16010.0, - 16092.0, - 15950.0, - 15972.0, - 15990.0, - 15947.0, - 16028.0, - 15987.0, - 16018.0, - 16129.0, - 16022.0, - 16092.0, - 16105.0, - 16243.0, - 16239.0, - 16180.0, - 16130.0, - 16109.0, - 16134.0, - 16164.0, - 16101.0, - 16041.0, - 16098.0, - 16129.0, - 16160.0, - 16113.0, - 16081.0, - 16056.0, - 16069.0, - 16087.0, - 16123.0, - 16116.0, - 16095.0, - 16059.0, - 16085.0, - 16138.0, - 16169.0, - 16150.0, - 16150.0, - 16163.0, - 16194.0, - 16108.0, - 16000.0, - 16036.0, - 16034.0, - 16004.0, - 16035.0, - 15756.0, - 15737.0, - 15704.0, - 15585.0, - 15764.0, - 15604.0, - 15708.0, - 15667.0, - 15756.0, - 15725.0, - 15633.0, - 15353.0, - 15272.0, - 15372.0, - 15378.0, - 15431.0, - 15392.0, - 15307.0, - 15251.0, - 15310.0, - 15717.0, - 15764.0, - 15585.0, - 15696.0, - 15658.0, - 15716.0, - 15719.0, - 16027.0, - 15897.0, - 16001.0, - 16016.0, - 16035.0, - 15842.0, - 15819.0, - 15813.0, - 15841.0, - 15854.0, - 15938.0, - 16046.0, - 15987.0, - 15943.0, - 15951.0, - 15900.0, - 15904.0, - 15910.0, - 15900.0, - 15912.0, - 15911.0, - 15885.0, - 15938.0, - 15853.0, - 15829.0, - 15712.0, - 15798.0, - 15822.0, - 15873.0, - 15884.0, - 15841.0, - 15845.0, - 15861.0, - 15851.0, - 15857.0, - 15976.0, - 15964.0, - 15989.0, - 15942.0, - 15951.0, - 15938.0, - 15970.0, - 15882.0, - 15891.0, - 15858.0, - 15869.0, - 15832.0, - 15847.0, - 15898.0, - 15886.0, - 15925.0, - 15893.0, - 15906.0, - 15908.0, - 15917.0, - 15877.0, - 15920.0, - 15967.0, - 15840.0, - 15591.0, - 15631.0, - 15613.0, - 15652.0, - 15528.0, - 15467.0, - 15473.0, - 15519.0, - 15635.0, - 15701.0, - 15675.0, - 15667.0, - 15692.0, - 15692.0, - 15842.0, - 15853.0, - 15917.0, - 15877.0, - 15870.0, - 15809.0, - 15837.0, - 15913.0, - 15925.0, - 15927.0, - 16290.0, - 16251.0, - 16273.0, - 16314.0, - 16291.0, - 16294.0, - 16224.0, - 16354.0, - 16258.0, - 16400.0, - 16427.0, - 16481.0, - 16418.0, - 16436.0, - 16390.0, - 16436.0, - 16404.0, - 16401.0, - 16292.0, - 16194.0, - 16161.0, - 16129.0, - 16106.0, - 16138.0, - 16096.0, - 16055.0, - 16082.0, - 16123.0, - 16130.0, - 16143.0, - 16166.0, - 16195.0, - 16114.0, - 16147.0, - 16165.0, - 16238.0, - 16184.0, - 16179.0, - 16172.0, - 16122.0, - 16132.0, - 16102.0, - 16111.0, - 16126.0, - 16119.0, - 16117.0, - 16095.0, - 16040.0, - 16133.0, - 16120.0, - 16174.0, - 16153.0, - 16166.0, - 16103.0, - 16108.0, - 16170.0, - 16196.0, - 16209.0, - 16249.0, - 16221.0, - 16339.0, - 16252.0, - 16390.0, - 16441.0, - 16401.0, - 16385.0, - 16306.0, - 16240.0, - 16242.0, - 16136.0, - 16187.0, - 16169.0, - 16200.0, - 16222.0, - 16177.0, - 16208.0, - 16185.0, - 16157.0, - 16170.0, - 16165.0, - 16182.0, - 16224.0, - 16223.0, - 16320.0, - 16268.0, - 16261.0, - 16253.0, - 16022.0, - 16081.0, - 16010.0, - 16002.0, - 16036.0, - 15983.0, - 16003.0, - 16031.0, - 16022.0, - 16026.0, - 16028.0, - 16014.0, - 16000.0, - 16002.0, - 16040.0, - 16008.0, - 16047.0, - 16076.0, - 16327.0, - 16263.0, - 16312.0, - 16285.0, - 16258.0, - 16274.0, - 16277.0, - 16321.0, - 16337.0, - 16280.0, - 16285.0, - 16276.0, - 16239.0, - 16229.0, - 16235.0, - 16276.0, - 16267.0, - 16264.0, - 16277.0, - 16265.0, - 16302.0, - 16273.0, - 16320.0, - 16276.0, - 16301.0, - 16267.0, - 16263.0, - 16299.0, - 16280.0, - 16302.0, - 16295.0, - 16306.0, - 16303.0, - 16263.0, - 16292.0, - 16308.0, - 16311.0, - 16247.0, - 16252.0, - 16121.0, - 16084.0, - 16113.0, - 16065.0, - 16070.0, - 16065.0, - 16057.0, - 16172.0, - 16149.0, - 16190.0, - 16288.0, - 16298.0, - 16237.0, - 16279.0, - 16268.0, - 16271.0, - 16483.0, - 16542.0, - 16794.0, - 16677.0, - 16600.0, - 16678.0, - 16707.0, - 16728.0, - 16718.0, - 16739.0, - 16731.0, - 16750.0, - 16706.0, - 16740.0, - 16791.0, - 16953.0, - 16992.0, - 16687.0, - 16683.0, - 16659.0, - 16538.0, - 16647.0, - 16614.0, - 16632.0, - 16671.0, - 16680.0, - 16445.0, - 16363.0, - 16414.0, - 16390.0, - 16353.0, - 16311.0, - 16309.0, - 16353.0, - 16340.0, - 16452.0, - 16044.0, - 16007.0, - 15914.0, - 15968.0, - 15821.0, - 15900.0, - 15874.0, - 15696.0, - 15763.0, - 15764.0, - 15753.0, - 15832.0, - 15759.0, - 15787.0, - 15754.0, - 15778.0, - 15751.0, - 15774.0, - 15788.0, - 15857.0, - 15781.0, - 15820.0, - 15822.0, - 15816.0, - 15779.0, - 15791.0, - 15792.0, - 15760.0, - 15781.0, - 15814.0, - 15809.0, - 15818.0, - 15843.0, - 15743.0, - 15743.0, - 15748.0, - 15757.0, - 15773.0, - 15741.0, - 15734.0, - 15596.0, - 15593.0, - 15627.0, - 15468.0, - 15603.0, - 15748.0, - 15831.0, - 15832.0, - 15801.0, - 15795.0, - 15801.0, - 15906.0, - 15839.0, - 15898.0, - 15877.0, - 15906.0, - 15868.0, - 15858.0, - 15839.0, - 15818.0, - 15901.0, - 15916.0, - 15846.0, - 15870.0, - 15867.0, - 15774.0, - 15837.0, - 15860.0, - 15820.0, - 15832.0, - 15811.0, - 15805.0, - 15828.0, - 15857.0, - 15793.0, - 15692.0, - 15678.0, - 15734.0, - 15843.0, - 15861.0, - 15825.0, - 15859.0, - 15857.0, - 15850.0, - 15866.0, - 15873.0, - 15883.0, - 15863.0, - 15865.0, - 15869.0, - 15839.0, - 15773.0, - 15832.0, - 15847.0, - 15814.0, - 15854.0, - 15854.0, - 15818.0, - 15809.0, - 15845.0, - 15848.0, - 15847.0, - 15834.0, - 15822.0, - 15826.0, - 15823.0, - 15843.0, - 15834.0, - 15854.0, - 15820.0, - 15830.0, - 15819.0, - 15815.0, - 15805.0, - 15842.0, - 15831.0, - 15879.0, - 15912.0, - 15848.0, - 15827.0, - 15837.0, - 15866.0, - 15842.0, - 15811.0, - 15837.0, - 15836.0, - 15815.0, - 15897.0, - 15820.0, - 15824.0, - 15802.0, - 15821.0, - 15807.0, - 15786.0, - 15823.0, - 15755.0, - 15675.0, - 15678.0, - 15700.0, - 15707.0, - 15673.0, - 15658.0, - 15592.0, - 15637.0, - 15631.0, - 15650.0, - 15619.0, - 15585.0, - 15647.0, - 15622.0, - 15550.0, - 15569.0, - 15544.0, - 15560.0, - 15606.0, - 15579.0, - 15578.0, - 15599.0, - 15578.0, - 15561.0, - 15532.0, - 15562.0, - 15565.0, - 15580.0, - 15585.0, - 15581.0, - 15529.0, - 15473.0, - 15501.0, - 15452.0, - 15438.0, - 15523.0, - 15415.0, - 15454.0, - 15497.0, - 15518.0, - 15479.0, - 15451.0, - 15452.0, - 15476.0, - 15445.0, - 15461.0, - 15510.0, - 15504.0, - 15489.0, - 15470.0, - 15444.0, - 15439.0, - 15437.0, - 15433.0, - 15425.0, - 15426.0, - 15439.0, - 15465.0, - 15462.0, - 15442.0, - 15466.0, - 15504.0, - 15516.0, - 15533.0, - 15483.0, - 15569.0, - 15549.0, - 15661.0, - 15653.0, - 15660.0, - 15629.0, - 15692.0, - 15666.0, - 15682.0, - 15682.0, - 15605.0, - 15639.0, - 15621.0, - 15637.0, - 15710.0, - 15859.0, - 15874.0, - 15833.0, - 15790.0, - 15763.0, - 15729.0, - 15770.0, - 15784.0, - 15803.0, - 15929.0, - 15921.0, - 15897.0, - 15869.0, - 15869.0, - 15856.0, - 15889.0, - 15936.0, - 15872.0, - 15849.0, - 15896.0, - 15838.0, - 15866.0, - 15863.0, - 15847.0, - 15797.0, - 15848.0, - 15895.0, - 16001.0, - 15970.0, - 16001.0, - 15992.0, - 16001.0, - 15975.0, - 15991.0, - 15988.0, - 15966.0, - 15953.0, - 15944.0, - 15887.0, - 15871.0, - 15823.0, - 15888.0, - 15891.0, - 15924.0, - 15926.0, - 15922.0, - 15910.0, - 15929.0, - 15888.0, - 15887.0, - 15901.0, - 15926.0, - 15919.0, - 15910.0, - 15904.0, - 15911.0, - 15912.0, - 15921.0, - 15923.0, - 15904.0, - 15911.0, - 15910.0, - 15883.0, - 15962.0, - 15883.0, - 15879.0, - 15918.0, - 16069.0, - 16114.0, - 16126.0, - 16091.0, - 16114.0, - 16156.0, - 16148.0, - 16070.0, - 16073.0, - 16150.0, - 16101.0, - 16016.0, - 16017.0, - 16027.0 - ], - "type": "scatter", - "xaxis": "x3", - "yaxis": "y3" - } - ], - "layout": { - "template": { - "data": { - "histogram2dcontour": [ - { - "type": "histogram2dcontour", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "choropleth": [ - { - "type": "choropleth", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - ], - "histogram2d": [ - { - "type": "histogram2d", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "heatmap": [ - { - "type": "heatmap", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "heatmapgl": [ - { - "type": "heatmapgl", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "contourcarpet": [ - { - "type": "contourcarpet", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - ], - "contour": [ - { - "type": "contour", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "surface": [ - { - "type": "surface", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - } - ], - "mesh3d": [ - { - "type": "mesh3d", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "parcoords": [ - { - "type": "parcoords", - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scatterpolargl": [ - { - "type": "scatterpolargl", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "scattergeo": [ - { - "type": "scattergeo", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scatterpolar": [ - { - "type": "scatterpolar", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "scattergl": [ - { - "type": "scattergl", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scatter3d": [ - { - "type": "scatter3d", - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scattermapbox": [ - { - "type": "scattermapbox", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scatterternary": [ - { - "type": "scatterternary", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "scattercarpet": [ - { - "type": "scattercarpet", - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - } - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ] - }, - "layout": { - "autotypenumbers": "strict", - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "hovermode": "closest", - "hoverlabel": { - "align": "left" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "bgcolor": "#E5ECF6", - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "ternary": { - "bgcolor": "#E5ECF6", - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "sequential": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ] - }, - "xaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "automargin": true, - "zerolinewidth": 2 - }, - "yaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "automargin": true, - "zerolinewidth": 2 - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "geo": { - "bgcolor": "white", - "landcolor": "#E5ECF6", - "subunitcolor": "white", - "showland": true, - "showlakes": true, - "lakecolor": "white" - }, - "title": { - "x": 0.05 - }, - "mapbox": { - "style": "light" - } - } - }, - "xaxis": { - "anchor": "y", - "domain": [ - 0.0, - 1.0 - ], - "title": { - "text": "Date" - } - }, - "yaxis": { - "anchor": "x", - "domain": [ - 0.68, - 1.0 - ], - "title": { - "text": "Prices down turn" - } - }, - "xaxis2": { - "anchor": "y2", - "domain": [ - 0.0, - 1.0 - ], - "title": { - "text": "Date" - } - }, - "yaxis2": { - "anchor": "x2", - "domain": [ - 0.34, - 0.66 - ], - "title": { - "text": "Prices up turn" - } - }, - "xaxis3": { - "anchor": "y3", - "domain": [ - 0.0, - 1.0 - ], - "title": { - "text": "Date" - } - }, - "yaxis3": { - "anchor": "x3", - "domain": [ - 0.0, - 0.32 - ], - "title": { - "text": "Prices side way" - } - }, - "title": { - "text": "Backtest date range close prices" - }, - "height": 600, - "width": 800 - }, - "config": { - "plotlyServerURL": "https://plot.ly" - } - }, - "text/html": "
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from investing_algorithm_framework import create_app, BacktestDateRange, CCXTOHLCVMarketDataSource, RESOURCE_DIRECTORY, PortfolioConfiguration\n", - "from plotly import graph_objects as go\n", - "from pandas import DataFrame as PandasDataFrame\n", - "from datetime import datetime\n", - "\n", - "# Define the backtest date ranges\n", - "up_turn_date_range = BacktestDateRange(\n", - " start_date=datetime(2022, 12, 20),\n", - " end_date=datetime(2023, 6, 1),\n", - " name=\"up_turn\"\n", - ")\n", - "sideways_date_range = BacktestDateRange(\n", - " start_date=datetime(2022, 6, 10),\n", - " end_date=datetime(2023, 1, 10),\n", - " name=\"sideways\"\n", - ")\n", - "down_turn_date_range = BacktestDateRange(\n", - " start_date=datetime(2021, 12, 21),\n", - " end_date=datetime(2022, 6, 20),\n", - " name=\"down_turn\"\n", - ")\n", - "\n", - "# Create the app and the champion and challenger algorithms\n", - "app = create_app(config={RESOURCE_DIRECTORY: \"resources_dump\"})\n", - "app.add_portfolio_configuration(PortfolioConfiguration(initial_balance=1000, trading_symbol=\"EUR\", market=\"BITVAVO\"))\n", - "\n", - "champion = create_primary_algorithm(\n", - " name=\"primary\",\n", - " description=\"This is the primary algorithm configured with 21 ema and 50 ema\",\n", - " long_period=50,\n", - " short_period=21\n", - ")\n", - "challenger = create_challenger_algorithm(\n", - " name=\"secondary\",\n", - " description=\"This is the primary algorithm configured with 21 ema and 50 ema\",\n", - " long_period=50,\n", - " short_period=21,\n", - " rsi_period=14,\n", - " rsi_sell_threshold=70,\n", - " rsi_buy_threshold=60\n", - ")\n", - "\n", - "def create_prices_graph(data: PandasDataFrame, name: str = \"Close\", color: str = \"blue\") -> go.Scatter:\n", - " return go.Scatter(\n", - " x=data.index,\n", - " y=data['Close'],\n", - " mode='lines',\n", - " line=dict(color=color, width=1),\n", - " name=name\n", - " )\n", - "\n", - "down_turn_data_source = CCXTOHLCVMarketDataSource(window_size=100, timeframe=\"2h\", market=\"bitvavo\", symbol=\"BTC/EUR\", identifier=\"BTC/EUR_OHLCV\")\n", - "df_down_turn = down_turn_data_source.get_data(start_date=down_turn_date_range.start_date, end_date=down_turn_date_range.end_date).to_pandas()\n", - "df_down_turn.set_index('Datetime', inplace=True)\n", - "up_turn_data_source = CCXTOHLCVMarketDataSource(window_size=100, timeframe=\"2h\", market=\"bitvavo\", symbol=\"BTC/EUR\", identifier=\"BTC/EUR_OHLCV\")\n", - "df_up_turn = up_turn_data_source.get_data(start_date=up_turn_date_range.start_date, end_date=up_turn_date_range.end_date).to_pandas()\n", - "df_up_turn.set_index('Datetime', inplace=True)\n", - "side_way_data_source = CCXTOHLCVMarketDataSource(window_size=100, timeframe=\"2h\", market=\"bitvavo\", symbol=\"BTC/EUR\", identifier=\"BTC/EUR_OHLCV\")\n", - "df_side_way = side_way_data_source.get_data(start_date=sideways_date_range.start_date, end_date=sideways_date_range.end_date).to_pandas()\n", - "df_side_way.set_index('Datetime', inplace=True)\n", - "\n", - "from plotly.subplots import make_subplots\n", - "fig = make_subplots(rows=3, cols=1, shared_xaxes=False, vertical_spacing=0.02)\n", - "fig.add_trace(create_prices_graph(df_down_turn, name=\"Close down turn\", color=\"red\"), row=1, col=1)\n", - "fig.add_trace(create_prices_graph(df_up_turn, name=\"Close up turn\", color=\"green\"), row=2, col=1)\n", - "fig.add_trace(create_prices_graph(df_side_way, name=\"Close side ways\", color=\"blue\"), row=3, col=1)\n", - "\n", - "fig.update_layout(height=600, width=800, title_text=\"Backtest date range close prices\")\n", - "\n", - "# Add titles to each subplot\n", - "fig.update_xaxes(title_text=\"Date\", row=1, col=1)\n", - "fig.update_yaxes(title_text=\"Prices down turn\", row=1, col=1)\n", - "fig.update_xaxes(title_text=\"Date\", row=2, col=1)\n", - "fig.update_yaxes(title_text=\"Prices up turn\", row=2, col=1)\n", - "fig.update_xaxes(title_text=\"Date\", row=3, col=1)\n", - "fig.update_yaxes(title_text=\"Prices side way\", row=3, col=1)\n", - "fig.show()" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "## Step 3: Running the backtests for all the date ranges" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92mdown_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 for a total of 2 algorithms.\u001B[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 41.78it/s]\n", - "Running backtest for algorithm with name primary: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:09<00:00, 230.93it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1036.78it/s]\n", - "Running backtest for algorithm with name secondary: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:09<00:00, 224.02it/s]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92mup_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 for a total of 2 algorithms.\u001B[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 185.70it/s]\n", - "Running backtest for algorithm with name primary: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:10<00:00, 195.16it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1174.88it/s]\n", - "Running backtest for algorithm with name secondary: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:07<00:00, 270.07it/s]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92msideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 for a total of 2 algorithms.\u001B[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 91.59it/s]\n", - "Running backtest for algorithm with name primary: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:11<00:00, 219.38it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1236.71it/s]\n", - "Running backtest for algorithm with name secondary: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:09<00:00, 268.31it/s]\n" - ] - } - ], - "source": [ - "reports = app.run_backtests(algorithms=[champion, challenger], date_ranges=[down_turn_date_range, up_turn_date_range, sideways_date_range])" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "## Step 4: Analyzing the backtest results" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " :%%%#+- .=*#%%% \u001B[92mBacktest reports evaluation\u001B[0m\n", - " *%%%%%%%+------=*%%%%%%%- \u001B[92m---------------------------\u001B[0m\n", - " *%%%%%%%%%%%%%%%%%%%%%%%- \u001B[93mNumber of reports:\u001B[0m \u001B[92m6 backtest reports\u001B[0m\n", - " .%%%%%%%%%%%%%%%%%%%%%%# \u001B[93mTotal number of date ranges:\u001B[0m\u001B[92m3\u001B[0m\n", - " #%%%####%%%%%%%%**#%%%+ \u001B[93mLargest overall profit:\u001B[0m\u001B[92m\u001B[0m\u001B[92m (Algorithm primary) 121.6183 EUR 12.1618% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001B[0m \n", - " .:-+*%%%%- \u001B[95m-+..#\u001B[0m%%%+.\u001B[95m+- +\u001B[0m%%%#*=-: \u001B[93mLargest overall growth:\u001B[0m\u001B[92m (Algorithm primary) 127.5655 EUR 12.7565% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001B[0m\n", - " .:-=*%%%%. \u001B[95m+=\u001B[0m .%%# \u001B[95m-+.-\u001B[0m%%%%=-:.. \n", - " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \n", - " +%%%%%%%%%%%%%%%%%%%= \n", - " :++ .=#%%%%%%%%%%%%%*- \n", - " :++: :+%%%%%%#-. \n", - " :++: .%%%%%#= \n", - " :++: .#%%%%%#*= \n", - " :++- :%%%%%%%%%+= \n", - " .++- -%%%%%%%%%%%+= \n", - " .++- .%%%%%%%%%%%%%+= \n", - " .++- *%%%%%%%%%%%%%*+: \n", - " .++- %%%%%%%%%%%%%%#+= \n", - " =++........:::%%%%%%%%%%%%%%*+- \n", - " .=++++++++++**#%%%%%%%%%%%%%++. \n", - " \n", - "\u001B[93mDate ranges of backtests:\u001B[0m\n", - "\u001B[92mdown_turn: 2021-12-21 00:00:00 - 2022-06-20 00:00:00\u001B[0m\n", - "\u001B[92mup_turn: 2022-12-20 00:00:00 - 2023-06-01 00:00:00\u001B[0m\n", - "\u001B[92msideways: 2022-06-10 00:00:00 - 2023-01-10 00:00:00\u001B[0m\n", - "\n", - "\u001B[93mAll profits ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Profit │ Profit percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ 121.6183 EUR │ 12.1618% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1127.57 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ 70.6731 EUR │ 7.0673% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1075.01 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ -3.3966 EUR │ -0.3397% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 941.031 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ -8.5178 EUR │ -0.8518% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.613 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ -50.7186 EUR │ -5.0719% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 949.281 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ -57.1643 EUR │ -5.7164% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 942.836 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", - "\u001B[93mAll growths ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Growth │ Growth percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ 127.5655 EUR │ 12.7565% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1127.57 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ 75.0139 EUR │ 7.5014% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1075.01 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ -2.3874 EUR │ -0.2387% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.613 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ -50.7186 EUR │ -5.0719% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 949.281 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary │ -57.1643 EUR │ -5.7164% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 942.836 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ secondary │ -58.9687 EUR │ -5.8969% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 941.031 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n" - ] - } - ], - "source": [ - "from investing_algorithm_framework import pretty_print_backtest_reports_evaluation, BacktestReportsEvaluation\n", - "\n", - "evaluation = BacktestReportsEvaluation(reports)\n", - "pretty_print_backtest_reports_evaluation(evaluation)" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": [ - "## Step 5: Highlighting the winning algorithm" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 13, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The winning algorithm is primary\n", - "\n", - " :%%%#+- .=*#%%% \u001B[92mBacktest report\u001B[0m\n", - " *%%%%%%%+------=*%%%%%%%- \u001B[92m---------------------------\u001B[0m\n", - " *%%%%%%%%%%%%%%%%%%%%%%%- \u001B[93mStart date:\u001B[0m\u001B[92m 2022-12-20 00:00:00\u001B[0m\n", - " .%%%%%%%%%%%%%%%%%%%%%%# \u001B[93mEnd date:\u001B[0m\u001B[92m 2023-06-01 00:00:00\u001B[0m\n", - " #%%%####%%%%%%%%**#%%%+ \u001B[93mNumber of days:\u001B[0m\u001B[92m\u001B[0m\u001B[92m 163\u001B[0m \n", - " .:-+*%%%%- \u001B[95m-+..#\u001B[0m%%%+.\u001B[95m+- +\u001B[0m%%%#*=-: \u001B[93mNumber of runs:\u001B[0m\u001B[92m 1957\u001B[0m\n", - " .:-=*%%%%. \u001B[95m+=\u001B[0m .%%# \u001B[95m-+.-\u001B[0m%%%%=-:.. \u001B[93mNumber of orders:\u001B[0m\u001B[92m 39\u001B[0m\n", - " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \u001B[93mInitial balance:\u001B[0m\u001B[92m 1000.0\u001B[0m\n", - " +%%%%%%%%%%%%%%%%%%%= \u001B[93mFinal balance:\u001B[0m\u001B[92m 1127.5655\u001B[0m\n", - " :++ .=#%%%%%%%%%%%%%*- \u001B[93mTotal net gain:\u001B[0m\u001B[92m 121.6183 12.16%\u001B[0m\n", - " :++: :+%%%%%%#-. \u001B[93mGrowth:\u001B[0m\u001B[92m 127.5655 12.76%\u001B[0m\n", - " :++: .%%%%%#= \u001B[93mNumber of trades closed:\u001B[0m\u001B[92m 19\u001B[0m\n", - " :++: .#%%%%%#*= \u001B[93mNumber of trades open(end of backtest):\u001B[0m\u001B[92m 1\u001B[0m\n", - " :++- :%%%%%%%%%+= \u001B[93mPercentage positive trades:\u001B[0m\u001B[92m 25.64102564102564%\u001B[0m\n", - " .++- -%%%%%%%%%%%+= \u001B[93mPercentage negative trades:\u001B[0m\u001B[92m 71.7948717948718%\u001B[0m\n", - " .++- .%%%%%%%%%%%%%+= \u001B[93mAverage trade size:\u001B[0m\u001B[92m 275.8284 EUR\u001B[0m\n", - " .++- *%%%%%%%%%%%%%*+: \u001B[93mAverage trade duration:\u001B[0m\u001B[92m 106.10526315789474 hours\u001B[0m\n", - " .++- %%%%%%%%%%%%%%#+= \n", - " =++........:::%%%%%%%%%%%%%%*+- \n", - " .=++++++++++**#%%%%%%%%%%%%%++. \n", - " \n", - "\u001B[93mPositions overview\u001B[0m\n", - "╭────────────┬──────────┬──────────────────────┬───────────────────────┬──────────────┬───────────────┬───────────────────────────┬────────────────┬───────────────╮\n", - "│ Position │ Amount │ Pending buy amount │ Pending sell amount │ Cost (EUR) │ Value (EUR) │ Percentage of portfolio │ Growth (EUR) │ Growth_rate │\n", - "├────────────┼──────────┼──────────────────────┼───────────────────────┼──────────────┼───────────────┼───────────────────────────┼────────────────┼───────────────┤\n", - "│ EUR │ 842.497 │ 0 │ 0 │ 842.497 │ 842.497 │ 74.7183% │ 0 │ 0.0000% │\n", - "├────────────┼──────────┼──────────────────────┼───────────────────────┼──────────────┼───────────────┼───────────────────────────┼────────────────┼───────────────┤\n", - "│ BTC │ 0.0112 │ 0 │ 0 │ 279.121 │ 285.068 │ 25.2817% │ 5.9472 │ 2.1307% │\n", - "╰────────────┴──────────┴──────────────────────┴───────────────────────┴──────────────┴───────────────┴───────────────────────────┴────────────────┴───────────────╯\n", - "\u001B[93mTrades overview\u001B[0m\n", - "╭─────────┬─────────────────────┬─────────────────────┬────────────────────┬──────────────┬──────────────────┬───────────────────────┬────────────────────┬─────────────────────╮\n", - "│ Pair │ Open date │ Close date │ Duration (hours) │ Size (EUR) │ Net gain (EUR) │ Net gain percentage │ Open price (EUR) │ Close price (EUR) │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-27 02:00:00 │ │ 8814.81 │ 279.121 │ 0 │ 0.0000% │ 24921.5 │ │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-23 04:00:00 │ 2023-05-24 14:00:00 │ 34 │ 281.263 │ -8.1696 │ -2.9046% │ 25339 │ 24603 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-20 22:00:00 │ 2023-05-21 18:00:00 │ 20 │ 280.482 │ -1.848 │ -0.6589% │ 25043 │ 24878 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-17 20:00:00 │ 2023-05-19 12:00:00 │ 40 │ 282.626 │ -4.0768 │ -1.4425% │ 25234.5 │ 24870.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-15 06:00:00 │ 2023-05-17 06:00:00 │ 48 │ 283.002 │ -4.2168 │ -1.4900% │ 25268 │ 24891.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-05-04 08:00:00 │ 2023-05-08 00:00:00 │ 88 │ 284.035 │ -2.8458 │ -1.0019% │ 26299.5 │ 26036 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-04-26 06:00:00 │ 2023-05-01 12:00:00 │ 126 │ 284.224 │ 1.4135 │ 0.4973% │ 25838.5 │ 25967 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-04-18 20:00:00 │ 2023-04-19 12:00:00 │ 16 │ 287.336 │ -8.9492 │ -3.1145% │ 27628.5 │ 26768 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-04-09 22:00:00 │ 2023-04-17 12:00:00 │ 182 │ 283.956 │ 11.0472 │ 3.8904% │ 26051 │ 27064.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-04-05 10:00:00 │ 2023-04-05 16:00:00 │ 6 │ 284.114 │ -2.9212 │ -1.0282% │ 26065.5 │ 25797.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-03-29 14:00:00 │ 2023-04-03 08:00:00 │ 114 │ 285.22 │ -4.7088 │ -1.6509% │ 26167 │ 25735 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-03-26 12:00:00 │ 2023-03-27 16:00:00 │ 28 │ 288.928 │ -9.5016 │ -3.2886% │ 26029.5 │ 25173.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-03-23 14:00:00 │ 2023-03-25 12:00:00 │ 46 │ 289.24 │ -2.7272 │ -0.9429% │ 25825 │ 25581.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-03-13 02:00:00 │ 2023-03-23 14:00:00 │ 252 │ 273.03 │ 58.747 │ 21.5166% │ 20842 │ 25326.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-02-15 08:00:00 │ 2023-02-22 16:00:00 │ 176 │ 268.248 │ 23.491 │ 8.7572% │ 20634.5 │ 22441.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-02-07 22:00:00 │ 2023-02-09 02:00:00 │ 28 │ 268.212 │ -2.5358 │ -0.9454% │ 21630 │ 21425.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-02-01 22:00:00 │ 2023-02-05 20:00:00 │ 94 │ 269.806 │ -4.9563 │ -1.8370% │ 21584.5 │ 21188 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2023-01-02 12:00:00 │ 2023-01-31 06:00:00 │ 690 │ 248.716 │ 85.8918 │ 34.5341% │ 15642.5 │ 21044.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2022-12-26 22:00:00 │ 2022-12-27 06:00:00 │ 8 │ 248.814 │ -0.3689 │ -0.1483% │ 15848 │ 15824.5 │\n", - "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼──────────────────┼───────────────────────┼────────────────────┼─────────────────────┤\n", - "│ BTC-EUR │ 2022-12-26 02:00:00 │ 2022-12-26 22:00:00 │ 20 │ 249.489 │ -1.1461 │ -0.4594% │ 15891 │ 15818 │\n", - "╰─────────┴─────────────────────┴─────────────────────┴────────────────────┴──────────────┴──────────────────┴───────────────────────┴────────────────────┴─────────────────────╯\n" - ] - }, - { - "data": { - "application/vnd.plotly.v1+json": { - "data": [ - { - "line": { - "color": "blue", - "width": 1 - }, - "mode": "lines", - "name": "Close price up turn", - "x": [ - "2022-12-20T00:00:00", - "2022-12-20T02:00:00", - "2022-12-20T04:00:00", - "2022-12-20T06:00:00", - "2022-12-20T08:00:00", - "2022-12-20T10:00:00", - "2022-12-20T12:00:00", - "2022-12-20T14:00:00", - "2022-12-20T16:00:00", - "2022-12-20T18:00:00", - "2022-12-20T20:00:00", - "2022-12-20T22:00:00", - "2022-12-21T00:00:00", - "2022-12-21T02:00:00", - "2022-12-21T04:00:00", - "2022-12-21T06:00:00", - "2022-12-21T08:00:00", - "2022-12-21T10:00:00", - "2022-12-21T12:00:00", - "2022-12-21T14:00:00", - "2022-12-21T16:00:00", - "2022-12-21T18:00:00", - "2022-12-21T20:00:00", - "2022-12-21T22:00:00", - "2022-12-22T00:00:00", - "2022-12-22T02:00:00", - "2022-12-22T04:00:00", - "2022-12-22T06:00:00", - "2022-12-22T08:00:00", - "2022-12-22T10:00:00", - "2022-12-22T12:00:00", - "2022-12-22T14:00:00", - "2022-12-22T16:00:00", - "2022-12-22T18:00:00", - "2022-12-22T20:00:00", - "2022-12-22T22:00:00", - "2022-12-23T00:00:00", - "2022-12-23T02:00:00", - "2022-12-23T04:00:00", - "2022-12-23T06:00:00", - "2022-12-23T08:00:00", - "2022-12-23T10:00:00", - "2022-12-23T12:00:00", - "2022-12-23T14:00:00", - "2022-12-23T16:00:00", - "2022-12-23T18:00:00", - "2022-12-23T20:00:00", - "2022-12-23T22:00:00", - "2022-12-24T00:00:00", - "2022-12-24T02:00:00", - "2022-12-24T04:00:00", - "2022-12-24T06:00:00", - "2022-12-24T08:00:00", - "2022-12-24T10:00:00", - "2022-12-24T12:00:00", - "2022-12-24T14:00:00", - "2022-12-24T16:00:00", - "2022-12-24T18:00:00", - "2022-12-24T20:00:00", - "2022-12-24T22:00:00", - "2022-12-25T00:00:00", - "2022-12-25T02:00:00", - "2022-12-25T04:00:00", - "2022-12-25T06:00:00", - "2022-12-25T08:00:00", - "2022-12-25T10:00:00", - "2022-12-25T12:00:00", - "2022-12-25T14:00:00", - "2022-12-25T16:00:00", - "2022-12-25T18:00:00", - "2022-12-25T20:00:00", - "2022-12-25T22:00:00", - "2022-12-26T00:00:00", - "2022-12-26T02:00:00", - "2022-12-26T04:00:00", - "2022-12-26T06:00:00", - "2022-12-26T08:00:00", - "2022-12-26T10:00:00", - "2022-12-26T12:00:00", - "2022-12-26T14:00:00", - "2022-12-26T16:00:00", - "2022-12-26T18:00:00", - "2022-12-26T20:00:00", - "2022-12-26T22:00:00", - "2022-12-27T00:00:00", - "2022-12-27T02:00:00", - "2022-12-27T04:00:00", - "2022-12-27T06:00:00", - "2022-12-27T08:00:00", - "2022-12-27T10:00:00", - "2022-12-27T12:00:00", - "2022-12-27T14:00:00", - "2022-12-27T16:00:00", - "2022-12-27T18:00:00", - "2022-12-27T20:00:00", - "2022-12-27T22:00:00", - "2022-12-28T00:00:00", - "2022-12-28T02:00:00", - "2022-12-28T04:00:00", - "2022-12-28T06:00:00", - "2022-12-28T08:00:00", - "2022-12-28T10:00:00", - "2022-12-28T12:00:00", - "2022-12-28T14:00:00", - "2022-12-28T16:00:00", - "2022-12-28T18:00:00", - "2022-12-28T20:00:00", - "2022-12-28T22:00:00", - "2022-12-29T00:00:00", - "2022-12-29T02:00:00", - "2022-12-29T04:00:00", - "2022-12-29T06:00:00", - "2022-12-29T08:00:00", - "2022-12-29T10:00:00", - "2022-12-29T12:00:00", - "2022-12-29T14:00:00", - "2022-12-29T16:00:00", - "2022-12-29T18:00:00", - "2022-12-29T20:00:00", - "2022-12-29T22:00:00", - "2022-12-30T00:00:00", - "2022-12-30T02:00:00", - "2022-12-30T04:00:00", - "2022-12-30T06:00:00", - "2022-12-30T08:00:00", - "2022-12-30T10:00:00", - "2022-12-30T12:00:00", - "2022-12-30T14:00:00", - "2022-12-30T16:00:00", - "2022-12-30T18:00:00", - "2022-12-30T20:00:00", - "2022-12-30T22:00:00", - "2022-12-31T00:00:00", - "2022-12-31T02:00:00", - "2022-12-31T04:00:00", - "2022-12-31T06:00:00", - "2022-12-31T08:00:00", - "2022-12-31T10:00:00", - "2022-12-31T12:00:00", - "2022-12-31T14:00:00", - "2022-12-31T16:00:00", - "2022-12-31T18:00:00", - "2022-12-31T20:00:00", - "2022-12-31T22:00:00", - "2023-01-01T00:00:00", - "2023-01-01T02:00:00", - "2023-01-01T04:00:00", - "2023-01-01T06:00:00", - "2023-01-01T08:00:00", - "2023-01-01T10:00:00", - "2023-01-01T12:00:00", - "2023-01-01T14:00:00", - "2023-01-01T16:00:00", - "2023-01-01T18:00:00", - "2023-01-01T20:00:00", - "2023-01-01T22:00:00", - "2023-01-02T00:00:00", - "2023-01-02T02:00:00", - "2023-01-02T04:00:00", - "2023-01-02T06:00:00", - "2023-01-02T08:00:00", - "2023-01-02T10:00:00", - "2023-01-02T12:00:00", - "2023-01-02T14:00:00", - "2023-01-02T16:00:00", - "2023-01-02T18:00:00", - "2023-01-02T20:00:00", - "2023-01-02T22:00:00", - "2023-01-03T00:00:00", - "2023-01-03T02:00:00", - "2023-01-03T04:00:00", - "2023-01-03T06:00:00", - "2023-01-03T08:00:00", - "2023-01-03T10:00:00", - "2023-01-03T12:00:00", - "2023-01-03T14:00:00", - "2023-01-03T16:00:00", - "2023-01-03T18:00:00", - "2023-01-03T20:00:00", - "2023-01-03T22:00:00", - "2023-01-04T00:00:00", - "2023-01-04T02:00:00", - "2023-01-04T04:00:00", - "2023-01-04T06:00:00", - "2023-01-04T08:00:00", - "2023-01-04T10:00:00", - "2023-01-04T12:00:00", - "2023-01-04T14:00:00", - "2023-01-04T16:00:00", - "2023-01-04T18:00:00", - "2023-01-04T20:00:00", - "2023-01-04T22:00:00", - "2023-01-05T00:00:00", - "2023-01-05T02:00:00", - "2023-01-05T04:00:00", - "2023-01-05T06:00:00", - "2023-01-05T08:00:00", - "2023-01-05T10:00:00", - "2023-01-05T12:00:00", - "2023-01-05T14:00:00", - "2023-01-05T16:00:00", - "2023-01-05T18:00:00", - "2023-01-05T20:00:00", - "2023-01-05T22:00:00", - "2023-01-06T00:00:00", - "2023-01-06T02:00:00", - "2023-01-06T04:00:00", - "2023-01-06T06:00:00", - "2023-01-06T08:00:00", - "2023-01-06T10:00:00", - "2023-01-06T12:00:00", - "2023-01-06T14:00:00", - "2023-01-06T16:00:00", - "2023-01-06T18:00:00", - "2023-01-06T20:00:00", - "2023-01-06T22:00:00", - "2023-01-07T00:00:00", - "2023-01-07T02:00:00", - "2023-01-07T04:00:00", - "2023-01-07T06:00:00", - "2023-01-07T08:00:00", - "2023-01-07T10:00:00", - "2023-01-07T12:00:00", - "2023-01-07T14:00:00", - "2023-01-07T16:00:00", - "2023-01-07T18:00:00", - "2023-01-07T20:00:00", - "2023-01-07T22:00:00", - "2023-01-08T00:00:00", - "2023-01-08T02:00:00", - "2023-01-08T04:00:00", - "2023-01-08T06:00:00", - "2023-01-08T08:00:00", - "2023-01-08T10:00:00", - "2023-01-08T12:00:00", - "2023-01-08T14:00:00", - "2023-01-08T16:00:00", - "2023-01-08T18:00:00", - "2023-01-08T20:00:00", - "2023-01-08T22:00:00", - "2023-01-09T00:00:00", - "2023-01-09T02:00:00", - "2023-01-09T04:00:00", - "2023-01-09T06:00:00", - "2023-01-09T08:00:00", - "2023-01-09T10:00:00", - "2023-01-09T12:00:00", - "2023-01-09T14:00:00", - "2023-01-09T16:00:00", - "2023-01-09T18:00:00", - "2023-01-09T20:00:00", - "2023-01-09T22:00:00", - "2023-01-10T00:00:00", - "2023-01-10T02:00:00", - "2023-01-10T04:00:00", - "2023-01-10T06:00:00", - "2023-01-10T08:00:00", - "2023-01-10T10:00:00", - "2023-01-10T12:00:00", - "2023-01-10T14:00:00", - "2023-01-10T16:00:00", - "2023-01-10T18:00:00", - "2023-01-10T20:00:00", - "2023-01-10T22:00:00", - "2023-01-11T00:00:00", - "2023-01-11T02:00:00", - "2023-01-11T04:00:00", - "2023-01-11T06:00:00", - "2023-01-11T08:00:00", - "2023-01-11T10:00:00", - "2023-01-11T12:00:00", - "2023-01-11T14:00:00", - "2023-01-11T16:00:00", - "2023-01-11T18:00:00", - "2023-01-11T20:00:00", - "2023-01-11T22:00:00", - "2023-01-12T00:00:00", - "2023-01-12T02:00:00", - "2023-01-12T04:00:00", - "2023-01-12T06:00:00", - "2023-01-12T08:00:00", - "2023-01-12T10:00:00", - "2023-01-12T12:00:00", - "2023-01-12T14:00:00", - "2023-01-12T16:00:00", - "2023-01-12T18:00:00", - "2023-01-12T20:00:00", - "2023-01-12T22:00:00", - "2023-01-13T00:00:00", - "2023-01-13T02:00:00", - "2023-01-13T04:00:00", - "2023-01-13T06:00:00", - "2023-01-13T08:00:00", - "2023-01-13T10:00:00", - "2023-01-13T12:00:00", - "2023-01-13T14:00:00", - "2023-01-13T16:00:00", - "2023-01-13T18:00:00", - "2023-01-13T20:00:00", - "2023-01-13T22:00:00", - "2023-01-14T00:00:00", - "2023-01-14T02:00:00", - "2023-01-14T04:00:00", - "2023-01-14T06:00:00", - "2023-01-14T08:00:00", - "2023-01-14T10:00:00", - "2023-01-14T12:00:00", - "2023-01-14T14:00:00", - "2023-01-14T16:00:00", - "2023-01-14T18:00:00", - "2023-01-14T20:00:00", - "2023-01-14T22:00:00", - "2023-01-15T00:00:00", - "2023-01-15T02:00:00", - "2023-01-15T04:00:00", - "2023-01-15T06:00:00", - "2023-01-15T08:00:00", - "2023-01-15T10:00:00", - "2023-01-15T12:00:00", - "2023-01-15T14:00:00", - "2023-01-15T16:00:00", - "2023-01-15T18:00:00", - "2023-01-15T20:00:00", - "2023-01-15T22:00:00", - "2023-01-16T00:00:00", - "2023-01-16T02:00:00", - "2023-01-16T04:00:00", - "2023-01-16T06:00:00", - "2023-01-16T08:00:00", - "2023-01-16T10:00:00", - "2023-01-16T12:00:00", - "2023-01-16T14:00:00", - "2023-01-16T16:00:00", - "2023-01-16T18:00:00", - "2023-01-16T20:00:00", - "2023-01-16T22:00:00", - "2023-01-17T00:00:00", - "2023-01-17T02:00:00", - "2023-01-17T04:00:00", - "2023-01-17T06:00:00", - "2023-01-17T08:00:00", - "2023-01-17T10:00:00", - "2023-01-17T12:00:00", - "2023-01-17T14:00:00", - "2023-01-17T16:00:00", - "2023-01-17T18:00:00", - "2023-01-17T20:00:00", - "2023-01-17T22:00:00", - "2023-01-18T00:00:00", - "2023-01-18T02:00:00", - "2023-01-18T04:00:00", - "2023-01-18T06:00:00", - "2023-01-18T08:00:00", - "2023-01-18T10:00:00", - "2023-01-18T12:00:00", - "2023-01-18T14:00:00", - "2023-01-18T16:00:00", - "2023-01-18T18:00:00", - "2023-01-18T20:00:00", - "2023-01-18T22:00:00", - "2023-01-19T00:00:00", - "2023-01-19T02:00:00", - "2023-01-19T04:00:00", - "2023-01-19T06:00:00", - "2023-01-19T08:00:00", - "2023-01-19T10:00:00", - "2023-01-19T12:00:00", - "2023-01-19T14:00:00", - "2023-01-19T16:00:00", - "2023-01-19T18:00:00", - "2023-01-19T20:00:00", - "2023-01-19T22:00:00", - "2023-01-20T00:00:00", - "2023-01-20T02:00:00", - "2023-01-20T04:00:00", - "2023-01-20T06:00:00", - "2023-01-20T08:00:00", - "2023-01-20T10:00:00", - "2023-01-20T12:00:00", - "2023-01-20T14:00:00", - "2023-01-20T16:00:00", - "2023-01-20T18:00:00", - "2023-01-20T20:00:00", - "2023-01-20T22:00:00", - "2023-01-21T00:00:00", - "2023-01-21T02:00:00", - "2023-01-21T04:00:00", - "2023-01-21T06:00:00", - "2023-01-21T08:00:00", - "2023-01-21T10:00:00", - "2023-01-21T12:00:00", - "2023-01-21T14:00:00", - "2023-01-21T16:00:00", - "2023-01-21T18:00:00", - "2023-01-21T20:00:00", - "2023-01-21T22:00:00", - "2023-01-22T00:00:00", - "2023-01-22T02:00:00", - "2023-01-22T04:00:00", - "2023-01-22T06:00:00", - "2023-01-22T08:00:00", - "2023-01-22T10:00:00", - "2023-01-22T12:00:00", - "2023-01-22T14:00:00", - "2023-01-22T16:00:00", - "2023-01-22T18:00:00", - "2023-01-22T20:00:00", - "2023-01-22T22:00:00", - "2023-01-23T00:00:00", - "2023-01-23T02:00:00", - "2023-01-23T04:00:00", - "2023-01-23T06:00:00", - "2023-01-23T08:00:00", - "2023-01-23T10:00:00", - "2023-01-23T12:00:00", - "2023-01-23T14:00:00", - "2023-01-23T16:00:00", - "2023-01-23T18:00:00", - "2023-01-23T20:00:00", - "2023-01-23T22:00:00", - "2023-01-24T00:00:00", - "2023-01-24T02:00:00", - "2023-01-24T04:00:00", - "2023-01-24T06:00:00", - "2023-01-24T08:00:00", - "2023-01-24T10:00:00", - "2023-01-24T12:00:00", - "2023-01-24T14:00:00", - "2023-01-24T16:00:00", - "2023-01-24T18:00:00", - "2023-01-24T20:00:00", - "2023-01-24T22:00:00", - "2023-01-25T00:00:00", - "2023-01-25T02:00:00", - "2023-01-25T04:00:00", - "2023-01-25T06:00:00", - "2023-01-25T08:00:00", - "2023-01-25T10:00:00", - "2023-01-25T12:00:00", - "2023-01-25T14:00:00", - "2023-01-25T16:00:00", - "2023-01-25T18:00:00", - "2023-01-25T20:00:00", - "2023-01-25T22:00:00", - "2023-01-26T00:00:00", - "2023-01-26T02:00:00", - "2023-01-26T04:00:00", - "2023-01-26T06:00:00", - "2023-01-26T08:00:00", - "2023-01-26T10:00:00", - "2023-01-26T12:00:00", - "2023-01-26T14:00:00", - "2023-01-26T16:00:00", - "2023-01-26T18:00:00", - "2023-01-26T20:00:00", - "2023-01-26T22:00:00", - "2023-01-27T00:00:00", - "2023-01-27T02:00:00", - "2023-01-27T04:00:00", - "2023-01-27T06:00:00", - "2023-01-27T08:00:00", - "2023-01-27T10:00:00", - "2023-01-27T12:00:00", - "2023-01-27T14:00:00", - "2023-01-27T16:00:00", - "2023-01-27T18:00:00", - "2023-01-27T20:00:00", - "2023-01-27T22:00:00", - "2023-01-28T00:00:00", - "2023-01-28T02:00:00", - "2023-01-28T04:00:00", - "2023-01-28T06:00:00", - "2023-01-28T08:00:00", - "2023-01-28T10:00:00", - "2023-01-28T12:00:00", - "2023-01-28T14:00:00", - "2023-01-28T16:00:00", - "2023-01-28T18:00:00", - "2023-01-28T20:00:00", - "2023-01-28T22:00:00", - "2023-01-29T00:00:00", - "2023-01-29T02:00:00", - "2023-01-29T04:00:00", - "2023-01-29T06:00:00", - "2023-01-29T08:00:00", - "2023-01-29T10:00:00", - "2023-01-29T12:00:00", - "2023-01-29T14:00:00", - "2023-01-29T16:00:00", - "2023-01-29T18:00:00", - "2023-01-29T20:00:00", - "2023-01-29T22:00:00", - "2023-01-30T00:00:00", - "2023-01-30T02:00:00", - "2023-01-30T04:00:00", - "2023-01-30T06:00:00", - "2023-01-30T08:00:00", - "2023-01-30T10:00:00", - "2023-01-30T12:00:00", - "2023-01-30T14:00:00", - "2023-01-30T16:00:00", - "2023-01-30T18:00:00", - "2023-01-30T20:00:00", - "2023-01-30T22:00:00", - "2023-01-31T00:00:00", - "2023-01-31T02:00:00", - "2023-01-31T04:00:00", - "2023-01-31T06:00:00", - "2023-01-31T08:00:00", - "2023-01-31T10:00:00", - "2023-01-31T12:00:00", - "2023-01-31T14:00:00", - "2023-01-31T16:00:00", - "2023-01-31T18:00:00", - "2023-01-31T20:00:00", - "2023-01-31T22:00:00", - "2023-02-01T00:00:00", - "2023-02-01T02:00:00", - "2023-02-01T04:00:00", - "2023-02-01T06:00:00", - "2023-02-01T08:00:00", - "2023-02-01T10:00:00", - "2023-02-01T12:00:00", - "2023-02-01T14:00:00", - "2023-02-01T16:00:00", - "2023-02-01T18:00:00", - "2023-02-01T20:00:00", - "2023-02-01T22:00:00", - "2023-02-02T00:00:00", - "2023-02-02T02:00:00", - "2023-02-02T04:00:00", - "2023-02-02T06:00:00", - "2023-02-02T08:00:00", - "2023-02-02T10:00:00", - "2023-02-02T12:00:00", - "2023-02-02T14:00:00", - "2023-02-02T16:00:00", - "2023-02-02T18:00:00", - "2023-02-02T20:00:00", - "2023-02-02T22:00:00", - "2023-02-03T00:00:00", - "2023-02-03T02:00:00", - "2023-02-03T04:00:00", - "2023-02-03T06:00:00", - "2023-02-03T08:00:00", - "2023-02-03T10:00:00", - "2023-02-03T12:00:00", - "2023-02-03T14:00:00", - "2023-02-03T16:00:00", - "2023-02-03T18:00:00", - "2023-02-03T20:00:00", - "2023-02-03T22:00:00", - "2023-02-04T00:00:00", - "2023-02-04T02:00:00", - "2023-02-04T04:00:00", - "2023-02-04T06:00:00", - "2023-02-04T08:00:00", - "2023-02-04T10:00:00", - "2023-02-04T12:00:00", - "2023-02-04T14:00:00", - "2023-02-04T16:00:00", - "2023-02-04T18:00:00", - "2023-02-04T20:00:00", - "2023-02-04T22:00:00", - "2023-02-05T00:00:00", - "2023-02-05T02:00:00", - "2023-02-05T04:00:00", - "2023-02-05T06:00:00", - "2023-02-05T08:00:00", - "2023-02-05T10:00:00", - "2023-02-05T12:00:00", - "2023-02-05T14:00:00", - "2023-02-05T16:00:00", - "2023-02-05T18:00:00", - "2023-02-05T20:00:00", - "2023-02-05T22:00:00", - "2023-02-06T00:00:00", - "2023-02-06T02:00:00", - "2023-02-06T04:00:00", - "2023-02-06T06:00:00", - "2023-02-06T08:00:00", - "2023-02-06T10:00:00", - "2023-02-06T12:00:00", - "2023-02-06T14:00:00", - "2023-02-06T16:00:00", - "2023-02-06T18:00:00", - "2023-02-06T20:00:00", - "2023-02-06T22:00:00", - "2023-02-07T00:00:00", - "2023-02-07T02:00:00", - "2023-02-07T04:00:00", - "2023-02-07T06:00:00", - "2023-02-07T08:00:00", - "2023-02-07T10:00:00", - "2023-02-07T12:00:00", - "2023-02-07T14:00:00", - "2023-02-07T16:00:00", - "2023-02-07T18:00:00", - "2023-02-07T20:00:00", - "2023-02-07T22:00:00", - "2023-02-08T00:00:00", - "2023-02-08T02:00:00", - "2023-02-08T04:00:00", - "2023-02-08T06:00:00", - "2023-02-08T08:00:00", - "2023-02-08T10:00:00", - "2023-02-08T12:00:00", - "2023-02-08T14:00:00", - "2023-02-08T16:00:00", - "2023-02-08T18:00:00", - "2023-02-08T20:00:00", - "2023-02-08T22:00:00", - "2023-02-09T00:00:00", - "2023-02-09T02:00:00", - "2023-02-09T04:00:00", - "2023-02-09T06:00:00", - "2023-02-09T08:00:00", - "2023-02-09T10:00:00", - "2023-02-09T12:00:00", - "2023-02-09T14:00:00", - "2023-02-09T16:00:00", - "2023-02-09T18:00:00", - "2023-02-09T20:00:00", - "2023-02-09T22:00:00", - "2023-02-10T00:00:00", - "2023-02-10T02:00:00", - "2023-02-10T04:00:00", - "2023-02-10T06:00:00", - "2023-02-10T08:00:00", - "2023-02-10T10:00:00", - "2023-02-10T12:00:00", - "2023-02-10T14:00:00", - "2023-02-10T16:00:00", - "2023-02-10T18:00:00", - "2023-02-10T20:00:00", - "2023-02-10T22:00:00", - "2023-02-11T00:00:00", - "2023-02-11T02:00:00", - "2023-02-11T04:00:00", - "2023-02-11T06:00:00", - "2023-02-11T08:00:00", - "2023-02-11T10:00:00", - "2023-02-11T12:00:00", - "2023-02-11T14:00:00", - "2023-02-11T16:00:00", - "2023-02-11T18:00:00", - "2023-02-11T20:00:00", - "2023-02-11T22:00:00", - "2023-02-12T00:00:00", - "2023-02-12T02:00:00", - "2023-02-12T04:00:00", - "2023-02-12T06:00:00", - "2023-02-12T08:00:00", - "2023-02-12T10:00:00", - "2023-02-12T12:00:00", - "2023-02-12T14:00:00", - "2023-02-12T16:00:00", - "2023-02-12T18:00:00", - "2023-02-12T20:00:00", - "2023-02-12T22:00:00", - "2023-02-13T00:00:00", - "2023-02-13T02:00:00", - "2023-02-13T04:00:00", - "2023-02-13T06:00:00", - "2023-02-13T08:00:00", - "2023-02-13T10:00:00", - "2023-02-13T12:00:00", - "2023-02-13T14:00:00", - "2023-02-13T16:00:00", - "2023-02-13T18:00:00", - "2023-02-13T20:00:00", - "2023-02-13T22:00:00", - "2023-02-14T00:00:00", - "2023-02-14T02:00:00", - "2023-02-14T04:00:00", - "2023-02-14T06:00:00", - "2023-02-14T08:00:00", - "2023-02-14T10:00:00", - "2023-02-14T12:00:00", - "2023-02-14T14:00:00", - "2023-02-14T16:00:00", - "2023-02-14T18:00:00", - "2023-02-14T20:00:00", - "2023-02-14T22:00:00", - "2023-02-15T00:00:00", - "2023-02-15T02:00:00", - "2023-02-15T04:00:00", - "2023-02-15T06:00:00", - "2023-02-15T08:00:00", - "2023-02-15T10:00:00", - "2023-02-15T12:00:00", - "2023-02-15T14:00:00", - "2023-02-15T16:00:00", - "2023-02-15T18:00:00", - "2023-02-15T20:00:00", - "2023-02-15T22:00:00", - "2023-02-16T00:00:00", - "2023-02-16T02:00:00", - "2023-02-16T04:00:00", - "2023-02-16T06:00:00", - "2023-02-16T08:00:00", - "2023-02-16T10:00:00", - "2023-02-16T12:00:00", - "2023-02-16T14:00:00", - "2023-02-16T16:00:00", - "2023-02-16T18:00:00", - "2023-02-16T20:00:00", - "2023-02-16T22:00:00", - "2023-02-17T00:00:00", - "2023-02-17T02:00:00", - "2023-02-17T04:00:00", - "2023-02-17T06:00:00", - "2023-02-17T08:00:00", - "2023-02-17T10:00:00", - "2023-02-17T12:00:00", - "2023-02-17T14:00:00", - "2023-02-17T16:00:00", - "2023-02-17T18:00:00", - "2023-02-17T20:00:00", - "2023-02-17T22:00:00", - "2023-02-18T00:00:00", - "2023-02-18T02:00:00", - "2023-02-18T04:00:00", - "2023-02-18T06:00:00", - "2023-02-18T08:00:00", - "2023-02-18T10:00:00", - "2023-02-18T12:00:00", - "2023-02-18T14:00:00", - "2023-02-18T16:00:00", - "2023-02-18T18:00:00", - "2023-02-18T20:00:00", - "2023-02-18T22:00:00", - "2023-02-19T00:00:00", - "2023-02-19T02:00:00", - "2023-02-19T04:00:00", - "2023-02-19T06:00:00", - "2023-02-19T08:00:00", - "2023-02-19T10:00:00", - "2023-02-19T12:00:00", - "2023-02-19T14:00:00", - "2023-02-19T16:00:00", - "2023-02-19T18:00:00", - "2023-02-19T20:00:00", - "2023-02-19T22:00:00", - "2023-02-20T00:00:00", - "2023-02-20T02:00:00", - "2023-02-20T04:00:00", - "2023-02-20T06:00:00", - "2023-02-20T08:00:00", - "2023-02-20T10:00:00", - "2023-02-20T12:00:00", - "2023-02-20T14:00:00", - "2023-02-20T16:00:00", - "2023-02-20T18:00:00", - "2023-02-20T20:00:00", - "2023-02-20T22:00:00", - "2023-02-21T00:00:00", - "2023-02-21T02:00:00", - "2023-02-21T04:00:00", - "2023-02-21T06:00:00", - "2023-02-21T08:00:00", - "2023-02-21T10:00:00", - "2023-02-21T12:00:00", - "2023-02-21T14:00:00", - "2023-02-21T16:00:00", - "2023-02-21T18:00:00", - "2023-02-21T20:00:00", - "2023-02-21T22:00:00", - "2023-02-22T00:00:00", - "2023-02-22T02:00:00", - "2023-02-22T04:00:00", - "2023-02-22T06:00:00", - "2023-02-22T08:00:00", - "2023-02-22T10:00:00", - "2023-02-22T12:00:00", - "2023-02-22T14:00:00", - "2023-02-22T16:00:00", - "2023-02-22T18:00:00", - "2023-02-22T20:00:00", - "2023-02-22T22:00:00", - "2023-02-23T00:00:00", - "2023-02-23T02:00:00", - "2023-02-23T04:00:00", - "2023-02-23T06:00:00", - "2023-02-23T08:00:00", - "2023-02-23T10:00:00", - "2023-02-23T12:00:00", - "2023-02-23T14:00:00", - "2023-02-23T16:00:00", - "2023-02-23T18:00:00", - "2023-02-23T20:00:00", - "2023-02-23T22:00:00", - "2023-02-24T00:00:00", - "2023-02-24T02:00:00", - "2023-02-24T04:00:00", - "2023-02-24T06:00:00", - "2023-02-24T08:00:00", - "2023-02-24T10:00:00", - "2023-02-24T12:00:00", - "2023-02-24T14:00:00", - "2023-02-24T16:00:00", - "2023-02-24T18:00:00", - "2023-02-24T20:00:00", - "2023-02-24T22:00:00", - "2023-02-25T00:00:00", - "2023-02-25T02:00:00", - "2023-02-25T04:00:00", - "2023-02-25T06:00:00", - "2023-02-25T08:00:00", - "2023-02-25T10:00:00", - "2023-02-25T12:00:00", - "2023-02-25T14:00:00", - "2023-02-25T16:00:00", - "2023-02-25T18:00:00", - "2023-02-25T20:00:00", - "2023-02-25T22:00:00", - "2023-02-26T00:00:00", - "2023-02-26T02:00:00", - "2023-02-26T04:00:00", - "2023-02-26T06:00:00", - "2023-02-26T08:00:00", - "2023-02-26T10:00:00", - "2023-02-26T12:00:00", - "2023-02-26T14:00:00", - "2023-02-26T16:00:00", - "2023-02-26T18:00:00", - "2023-02-26T20:00:00", - "2023-02-26T22:00:00", - "2023-02-27T00:00:00", - "2023-02-27T02:00:00", - "2023-02-27T04:00:00", - "2023-02-27T06:00:00", - "2023-02-27T08:00:00", - "2023-02-27T10:00:00", - "2023-02-27T12:00:00", - "2023-02-27T14:00:00", - "2023-02-27T16:00:00", - "2023-02-27T18:00:00", - "2023-02-27T20:00:00", - "2023-02-27T22:00:00", - "2023-02-28T00:00:00", - "2023-02-28T02:00:00", - "2023-02-28T04:00:00", - "2023-02-28T06:00:00", - "2023-02-28T08:00:00", - "2023-02-28T10:00:00", - "2023-02-28T12:00:00", - "2023-02-28T14:00:00", - "2023-02-28T16:00:00", - "2023-02-28T18:00:00", - "2023-02-28T20:00:00", - "2023-02-28T22:00:00", - "2023-03-01T00:00:00", - "2023-03-01T02:00:00", - "2023-03-01T04:00:00", - "2023-03-01T06:00:00", - "2023-03-01T08:00:00", - "2023-03-01T10:00:00", - "2023-03-01T12:00:00", - "2023-03-01T14:00:00", - "2023-03-01T16:00:00", - "2023-03-01T18:00:00", - "2023-03-01T20:00:00", - "2023-03-01T22:00:00", - "2023-03-02T00:00:00", - "2023-03-02T02:00:00", - "2023-03-02T04:00:00", - "2023-03-02T06:00:00", - "2023-03-02T08:00:00", - "2023-03-02T10:00:00", - "2023-03-02T12:00:00", - "2023-03-02T14:00:00", - "2023-03-02T16:00:00", - "2023-03-02T18:00:00", - "2023-03-02T20:00:00", - "2023-03-02T22:00:00", - "2023-03-03T00:00:00", - "2023-03-03T02:00:00", - "2023-03-03T04:00:00", - "2023-03-03T06:00:00", - "2023-03-03T08:00:00", - "2023-03-03T10:00:00", - "2023-03-03T12:00:00", - "2023-03-03T14:00:00", - "2023-03-03T16:00:00", - "2023-03-03T18:00:00", - "2023-03-03T20:00:00", - "2023-03-03T22:00:00", - "2023-03-04T00:00:00", - "2023-03-04T02:00:00", - "2023-03-04T04:00:00", - "2023-03-04T06:00:00", - "2023-03-04T08:00:00", - "2023-03-04T10:00:00", - "2023-03-04T12:00:00", - "2023-03-04T14:00:00", - "2023-03-04T16:00:00", - "2023-03-04T18:00:00", - "2023-03-04T20:00:00", - "2023-03-04T22:00:00", - "2023-03-05T00:00:00", - "2023-03-05T02:00:00", - "2023-03-05T04:00:00", - "2023-03-05T06:00:00", - "2023-03-05T08:00:00", - "2023-03-05T10:00:00", - "2023-03-05T12:00:00", - "2023-03-05T14:00:00", - "2023-03-05T16:00:00", - "2023-03-05T18:00:00", - "2023-03-05T20:00:00", - "2023-03-05T22:00:00", - "2023-03-06T00:00:00", - "2023-03-06T02:00:00", - "2023-03-06T04:00:00", - "2023-03-06T06:00:00", - "2023-03-06T08:00:00", - "2023-03-06T10:00:00", - "2023-03-06T12:00:00", - "2023-03-06T14:00:00", - "2023-03-06T16:00:00", - "2023-03-06T18:00:00", - "2023-03-06T20:00:00", - "2023-03-06T22:00:00", - "2023-03-07T00:00:00", - "2023-03-07T02:00:00", - "2023-03-07T04:00:00", - "2023-03-07T06:00:00", - "2023-03-07T08:00:00", - "2023-03-07T10:00:00", - "2023-03-07T12:00:00", - "2023-03-07T14:00:00", - "2023-03-07T16:00:00", - "2023-03-07T18:00:00", - "2023-03-07T20:00:00", - "2023-03-07T22:00:00", - "2023-03-08T00:00:00", - "2023-03-08T02:00:00", - "2023-03-08T04:00:00", - "2023-03-08T06:00:00", - "2023-03-08T08:00:00", - "2023-03-08T10:00:00", - "2023-03-08T12:00:00", - "2023-03-08T14:00:00", - "2023-03-08T16:00:00", - "2023-03-08T18:00:00", - "2023-03-08T20:00:00", - "2023-03-08T22:00:00", - "2023-03-09T00:00:00", - "2023-03-09T02:00:00", - "2023-03-09T04:00:00", - "2023-03-09T06:00:00", - "2023-03-09T08:00:00", - "2023-03-09T10:00:00", - "2023-03-09T12:00:00", - "2023-03-09T14:00:00", - "2023-03-09T16:00:00", - "2023-03-09T18:00:00", - "2023-03-09T20:00:00", - "2023-03-09T22:00:00", - "2023-03-10T00:00:00", - "2023-03-10T02:00:00", - "2023-03-10T04:00:00", - "2023-03-10T06:00:00", - "2023-03-10T08:00:00", - "2023-03-10T10:00:00", - "2023-03-10T12:00:00", - "2023-03-10T14:00:00", - "2023-03-10T16:00:00", - "2023-03-10T18:00:00", - "2023-03-10T20:00:00", - "2023-03-10T22:00:00", - "2023-03-11T00:00:00", - "2023-03-11T02:00:00", - "2023-03-11T04:00:00", - "2023-03-11T06:00:00", - "2023-03-11T08:00:00", - "2023-03-11T10:00:00", - "2023-03-11T12:00:00", - "2023-03-11T14:00:00", - "2023-03-11T16:00:00", - "2023-03-11T18:00:00", - "2023-03-11T20:00:00", - "2023-03-11T22:00:00", - "2023-03-12T00:00:00", - "2023-03-12T02:00:00", - "2023-03-12T04:00:00", - "2023-03-12T06:00:00", - "2023-03-12T08:00:00", - "2023-03-12T10:00:00", - "2023-03-12T12:00:00", - "2023-03-12T14:00:00", - "2023-03-12T16:00:00", - "2023-03-12T18:00:00", - "2023-03-12T20:00:00", - "2023-03-12T22:00:00", - "2023-03-13T00:00:00", - "2023-03-13T02:00:00", - "2023-03-13T04:00:00", - "2023-03-13T06:00:00", - "2023-03-13T08:00:00", - "2023-03-13T10:00:00", - "2023-03-13T12:00:00", - "2023-03-13T14:00:00", - "2023-03-13T16:00:00", - "2023-03-13T18:00:00", - "2023-03-13T20:00:00", - "2023-03-13T22:00:00", - "2023-03-14T00:00:00", - "2023-03-14T02:00:00", - "2023-03-14T04:00:00", - "2023-03-14T06:00:00", - "2023-03-14T08:00:00", - "2023-03-14T10:00:00", - "2023-03-14T12:00:00", - "2023-03-14T14:00:00", - "2023-03-14T16:00:00", - "2023-03-14T18:00:00", - "2023-03-14T20:00:00", - "2023-03-14T22:00:00", - "2023-03-15T00:00:00", - "2023-03-15T02:00:00", - "2023-03-15T04:00:00", - "2023-03-15T06:00:00", - "2023-03-15T08:00:00", - "2023-03-15T10:00:00", - "2023-03-15T12:00:00", - "2023-03-15T14:00:00", - "2023-03-15T16:00:00", - "2023-03-15T18:00:00", - "2023-03-15T20:00:00", - "2023-03-15T22:00:00", - "2023-03-16T00:00:00", - "2023-03-16T02:00:00", - "2023-03-16T04:00:00", - "2023-03-16T06:00:00", - "2023-03-16T08:00:00", - "2023-03-16T10:00:00", - "2023-03-16T12:00:00", - "2023-03-16T14:00:00", - "2023-03-16T16:00:00", - "2023-03-16T18:00:00", - "2023-03-16T20:00:00", - "2023-03-16T22:00:00", - "2023-03-17T00:00:00", - "2023-03-17T02:00:00", - "2023-03-17T04:00:00", - "2023-03-17T06:00:00", - "2023-03-17T08:00:00", - "2023-03-17T10:00:00", - "2023-03-17T12:00:00", - "2023-03-17T14:00:00", - "2023-03-17T16:00:00", - "2023-03-17T18:00:00", - "2023-03-17T20:00:00", - "2023-03-17T22:00:00", - "2023-03-18T00:00:00", - "2023-03-18T02:00:00", - "2023-03-18T04:00:00", - "2023-03-18T06:00:00", - "2023-03-18T08:00:00", - "2023-03-18T10:00:00", - "2023-03-18T12:00:00", - "2023-03-18T14:00:00", - "2023-03-18T16:00:00", - "2023-03-18T18:00:00", - "2023-03-18T20:00:00", - "2023-03-18T22:00:00", - "2023-03-19T00:00:00", - "2023-03-19T02:00:00", - "2023-03-19T04:00:00", - "2023-03-19T06:00:00", - "2023-03-19T08:00:00", - "2023-03-19T10:00:00", - "2023-03-19T12:00:00", - "2023-03-19T14:00:00", - "2023-03-19T16:00:00", - "2023-03-19T18:00:00", - "2023-03-19T20:00:00", - "2023-03-19T22:00:00", - "2023-03-20T00:00:00", - "2023-03-20T02:00:00", - "2023-03-20T04:00:00", - "2023-03-20T06:00:00", - "2023-03-20T08:00:00", - "2023-03-20T10:00:00", - "2023-03-20T12:00:00", - "2023-03-20T14:00:00", - "2023-03-20T16:00:00", - "2023-03-20T18:00:00", - "2023-03-20T20:00:00", - "2023-03-20T22:00:00", - "2023-03-21T00:00:00", - "2023-03-21T02:00:00", - "2023-03-21T04:00:00", - "2023-03-21T06:00:00", - "2023-03-21T08:00:00", - "2023-03-21T10:00:00", - "2023-03-21T12:00:00", - "2023-03-21T14:00:00", - "2023-03-21T16:00:00", - "2023-03-21T18:00:00", - "2023-03-21T20:00:00", - "2023-03-21T22:00:00", - "2023-03-22T00:00:00", - "2023-03-22T02:00:00", - "2023-03-22T04:00:00", - "2023-03-22T06:00:00", - "2023-03-22T08:00:00", - "2023-03-22T10:00:00", - "2023-03-22T12:00:00", - "2023-03-22T14:00:00", - "2023-03-22T16:00:00", - "2023-03-22T18:00:00", - "2023-03-22T20:00:00", - "2023-03-22T22:00:00", - "2023-03-23T00:00:00", - "2023-03-23T02:00:00", - "2023-03-23T04:00:00", - "2023-03-23T06:00:00", - "2023-03-23T08:00:00", - "2023-03-23T10:00:00", - "2023-03-23T12:00:00", - "2023-03-23T14:00:00", - "2023-03-23T16:00:00", - "2023-03-23T18:00:00", - "2023-03-23T20:00:00", - "2023-03-23T22:00:00", - "2023-03-24T00:00:00", - "2023-03-24T02:00:00", - "2023-03-24T04:00:00", - "2023-03-24T06:00:00", - "2023-03-24T08:00:00", - "2023-03-24T10:00:00", - "2023-03-24T12:00:00", - "2023-03-24T14:00:00", - "2023-03-24T16:00:00", - "2023-03-24T18:00:00", - "2023-03-24T20:00:00", - "2023-03-24T22:00:00", - "2023-03-25T00:00:00", - "2023-03-25T02:00:00", - "2023-03-25T04:00:00", - "2023-03-25T06:00:00", - "2023-03-25T08:00:00", - "2023-03-25T10:00:00", - "2023-03-25T12:00:00", - "2023-03-25T14:00:00", - "2023-03-25T16:00:00", - "2023-03-25T18:00:00", - "2023-03-25T20:00:00", - "2023-03-25T22:00:00", - "2023-03-26T00:00:00", - "2023-03-26T02:00:00", - "2023-03-26T04:00:00", - "2023-03-26T06:00:00", - "2023-03-26T08:00:00", - "2023-03-26T10:00:00", - "2023-03-26T12:00:00", - "2023-03-26T14:00:00", - "2023-03-26T16:00:00", - "2023-03-26T18:00:00", - "2023-03-26T20:00:00", - "2023-03-26T22:00:00", - "2023-03-27T00:00:00", - "2023-03-27T02:00:00", - "2023-03-27T04:00:00", - "2023-03-27T06:00:00", - "2023-03-27T08:00:00", - "2023-03-27T10:00:00", - "2023-03-27T12:00:00", - "2023-03-27T14:00:00", - "2023-03-27T16:00:00", - "2023-03-27T18:00:00", - "2023-03-27T20:00:00", - "2023-03-27T22:00:00", - "2023-03-28T00:00:00", - "2023-03-28T02:00:00", - "2023-03-28T04:00:00", - "2023-03-28T06:00:00", - "2023-03-28T08:00:00", - "2023-03-28T10:00:00", - "2023-03-28T12:00:00", - "2023-03-28T14:00:00", - "2023-03-28T16:00:00", - "2023-03-28T18:00:00", - "2023-03-28T20:00:00", - "2023-03-28T22:00:00", - "2023-03-29T00:00:00", - "2023-03-29T02:00:00", - "2023-03-29T04:00:00", - "2023-03-29T06:00:00", - "2023-03-29T08:00:00", - "2023-03-29T10:00:00", - "2023-03-29T12:00:00", - "2023-03-29T14:00:00", - "2023-03-29T16:00:00", - "2023-03-29T18:00:00", - "2023-03-29T20:00:00", - "2023-03-29T22:00:00", - "2023-03-30T00:00:00", - "2023-03-30T02:00:00", - "2023-03-30T04:00:00", - "2023-03-30T06:00:00", - "2023-03-30T08:00:00", - "2023-03-30T10:00:00", - "2023-03-30T12:00:00", - "2023-03-30T14:00:00", - "2023-03-30T16:00:00", - "2023-03-30T18:00:00", - "2023-03-30T20:00:00", - "2023-03-30T22:00:00", - "2023-03-31T00:00:00", - "2023-03-31T02:00:00", - "2023-03-31T04:00:00", - "2023-03-31T06:00:00", - "2023-03-31T08:00:00", - "2023-03-31T10:00:00", - "2023-03-31T12:00:00", - "2023-03-31T14:00:00", - "2023-03-31T16:00:00", - "2023-03-31T18:00:00", - "2023-03-31T20:00:00", - "2023-03-31T22:00:00", - "2023-04-01T00:00:00", - "2023-04-01T02:00:00", - "2023-04-01T04:00:00", - "2023-04-01T06:00:00", - "2023-04-01T08:00:00", - "2023-04-01T10:00:00", - "2023-04-01T12:00:00", - "2023-04-01T14:00:00", - "2023-04-01T16:00:00", - "2023-04-01T18:00:00", - "2023-04-01T20:00:00", - "2023-04-01T22:00:00", - "2023-04-02T00:00:00", - "2023-04-02T02:00:00", - "2023-04-02T04:00:00", - "2023-04-02T06:00:00", - "2023-04-02T08:00:00", - "2023-04-02T10:00:00", - "2023-04-02T12:00:00", - "2023-04-02T14:00:00", - "2023-04-02T16:00:00", - "2023-04-02T18:00:00", - "2023-04-02T20:00:00", - "2023-04-02T22:00:00", - "2023-04-03T00:00:00", - "2023-04-03T02:00:00", - "2023-04-03T04:00:00", - "2023-04-03T06:00:00", - "2023-04-03T08:00:00", - "2023-04-03T10:00:00", - "2023-04-03T12:00:00", - "2023-04-03T14:00:00", - "2023-04-03T16:00:00", - "2023-04-03T18:00:00", - "2023-04-03T20:00:00", - "2023-04-03T22:00:00", - "2023-04-04T00:00:00", - "2023-04-04T02:00:00", - "2023-04-04T04:00:00", - "2023-04-04T06:00:00", - "2023-04-04T08:00:00", - "2023-04-04T10:00:00", - "2023-04-04T12:00:00", - "2023-04-04T14:00:00", - "2023-04-04T16:00:00", - "2023-04-04T18:00:00", - "2023-04-04T20:00:00", - "2023-04-04T22:00:00", - "2023-04-05T00:00:00", - "2023-04-05T02:00:00", - "2023-04-05T04:00:00", - "2023-04-05T06:00:00", - "2023-04-05T08:00:00", - "2023-04-05T10:00:00", - "2023-04-05T12:00:00", - "2023-04-05T14:00:00", - "2023-04-05T16:00:00", - "2023-04-05T18:00:00", - "2023-04-05T20:00:00", - "2023-04-05T22:00:00", - "2023-04-06T00:00:00", - "2023-04-06T02:00:00", - "2023-04-06T04:00:00", - "2023-04-06T06:00:00", - "2023-04-06T08:00:00", - "2023-04-06T10:00:00", - "2023-04-06T12:00:00", - "2023-04-06T14:00:00", - "2023-04-06T16:00:00", - "2023-04-06T18:00:00", - "2023-04-06T20:00:00", - "2023-04-06T22:00:00", - "2023-04-07T00:00:00", - "2023-04-07T02:00:00", - "2023-04-07T04:00:00", - "2023-04-07T06:00:00", - "2023-04-07T08:00:00", - "2023-04-07T10:00:00", - "2023-04-07T12:00:00", - "2023-04-07T14:00:00", - "2023-04-07T16:00:00", - "2023-04-07T18:00:00", - "2023-04-07T20:00:00", - "2023-04-07T22:00:00", - "2023-04-08T00:00:00", - "2023-04-08T02:00:00", - "2023-04-08T04:00:00", - "2023-04-08T06:00:00", - "2023-04-08T08:00:00", - "2023-04-08T10:00:00", - "2023-04-08T12:00:00", - "2023-04-08T14:00:00", - "2023-04-08T16:00:00", - "2023-04-08T18:00:00", - "2023-04-08T20:00:00", - "2023-04-08T22:00:00", - "2023-04-09T00:00:00", - "2023-04-09T02:00:00", - "2023-04-09T04:00:00", - "2023-04-09T06:00:00", - "2023-04-09T08:00:00", - "2023-04-09T10:00:00", - "2023-04-09T12:00:00", - "2023-04-09T14:00:00", - "2023-04-09T16:00:00", - "2023-04-09T18:00:00", - "2023-04-09T20:00:00", - "2023-04-09T22:00:00", - "2023-04-10T00:00:00", - "2023-04-10T02:00:00", - "2023-04-10T04:00:00", - "2023-04-10T06:00:00", - "2023-04-10T08:00:00", - "2023-04-10T10:00:00", - "2023-04-10T12:00:00", - "2023-04-10T14:00:00", - "2023-04-10T16:00:00", - "2023-04-10T18:00:00", - "2023-04-10T20:00:00", - "2023-04-10T22:00:00", - "2023-04-11T00:00:00", - "2023-04-11T02:00:00", - "2023-04-11T04:00:00", - "2023-04-11T06:00:00", - "2023-04-11T08:00:00", - "2023-04-11T10:00:00", - "2023-04-11T12:00:00", - "2023-04-11T14:00:00", - "2023-04-11T16:00:00", - "2023-04-11T18:00:00", - "2023-04-11T20:00:00", - "2023-04-11T22:00:00", - "2023-04-12T00:00:00", - "2023-04-12T02:00:00", - "2023-04-12T04:00:00", - "2023-04-12T06:00:00", - "2023-04-12T08:00:00", - "2023-04-12T10:00:00", - "2023-04-12T12:00:00", - "2023-04-12T14:00:00", - "2023-04-12T16:00:00", - "2023-04-12T18:00:00", - "2023-04-12T20:00:00", - "2023-04-12T22:00:00", - "2023-04-13T00:00:00", - "2023-04-13T02:00:00", - "2023-04-13T04:00:00", - "2023-04-13T06:00:00", - "2023-04-13T08:00:00", - "2023-04-13T10:00:00", - "2023-04-13T12:00:00", - "2023-04-13T14:00:00", - "2023-04-13T16:00:00", - "2023-04-13T18:00:00", - "2023-04-13T20:00:00", - "2023-04-13T22:00:00", - "2023-04-14T00:00:00", - "2023-04-14T02:00:00", - "2023-04-14T04:00:00", - "2023-04-14T06:00:00", - "2023-04-14T08:00:00", - "2023-04-14T10:00:00", - "2023-04-14T12:00:00", - "2023-04-14T14:00:00", - "2023-04-14T16:00:00", - "2023-04-14T18:00:00", - "2023-04-14T20:00:00", - "2023-04-14T22:00:00", - "2023-04-15T00:00:00", - "2023-04-15T02:00:00", - "2023-04-15T04:00:00", - "2023-04-15T06:00:00", - "2023-04-15T08:00:00", - "2023-04-15T10:00:00", - "2023-04-15T12:00:00", - "2023-04-15T14:00:00", - "2023-04-15T16:00:00", - "2023-04-15T18:00:00", - "2023-04-15T20:00:00", - "2023-04-15T22:00:00", - "2023-04-16T00:00:00", - "2023-04-16T04:00:00", - "2023-04-16T06:00:00", - "2023-04-16T08:00:00", - "2023-04-16T10:00:00", - "2023-04-16T12:00:00", - "2023-04-16T14:00:00", - "2023-04-16T16:00:00", - "2023-04-16T18:00:00", - "2023-04-16T20:00:00", - "2023-04-16T22:00:00", - "2023-04-17T00:00:00", - "2023-04-17T02:00:00", - "2023-04-17T04:00:00", - "2023-04-17T06:00:00", - "2023-04-17T08:00:00", - "2023-04-17T10:00:00", - "2023-04-17T12:00:00", - "2023-04-17T14:00:00", - "2023-04-17T16:00:00", - "2023-04-17T18:00:00", - "2023-04-17T20:00:00", - "2023-04-17T22:00:00", - "2023-04-18T00:00:00", - "2023-04-18T02:00:00", - "2023-04-18T04:00:00", - "2023-04-18T06:00:00", - "2023-04-18T08:00:00", - "2023-04-18T10:00:00", - "2023-04-18T12:00:00", - "2023-04-18T14:00:00", - "2023-04-18T16:00:00", - "2023-04-18T18:00:00", - "2023-04-18T20:00:00", - "2023-04-18T22:00:00", - "2023-04-19T00:00:00", - "2023-04-19T02:00:00", - "2023-04-19T04:00:00", - "2023-04-19T06:00:00", - "2023-04-19T08:00:00", - "2023-04-19T10:00:00", - "2023-04-19T12:00:00", - "2023-04-19T14:00:00", - "2023-04-19T16:00:00", - "2023-04-19T18:00:00", - "2023-04-19T20:00:00", - "2023-04-19T22:00:00", - "2023-04-20T00:00:00", - "2023-04-20T02:00:00", - "2023-04-20T04:00:00", - "2023-04-20T06:00:00", - "2023-04-20T08:00:00", - "2023-04-20T10:00:00", - "2023-04-20T12:00:00", - "2023-04-20T14:00:00", - "2023-04-20T16:00:00", - "2023-04-20T18:00:00", - "2023-04-20T20:00:00", - "2023-04-20T22:00:00", - "2023-04-21T00:00:00", - "2023-04-21T02:00:00", - "2023-04-21T04:00:00", - "2023-04-21T06:00:00", - "2023-04-21T08:00:00", - "2023-04-21T10:00:00", - "2023-04-21T12:00:00", - "2023-04-21T14:00:00", - "2023-04-21T16:00:00", - "2023-04-21T18:00:00", - "2023-04-21T20:00:00", - "2023-04-21T22:00:00", - "2023-04-22T00:00:00", - "2023-04-22T02:00:00", - "2023-04-22T04:00:00", - "2023-04-22T06:00:00", - "2023-04-22T08:00:00", - "2023-04-22T10:00:00", - "2023-04-22T12:00:00", - "2023-04-22T14:00:00", - "2023-04-22T16:00:00", - "2023-04-22T18:00:00", - "2023-04-22T20:00:00", - "2023-04-22T22:00:00", - "2023-04-23T00:00:00", - "2023-04-23T02:00:00", - "2023-04-23T04:00:00", - "2023-04-23T06:00:00", - "2023-04-23T08:00:00", - "2023-04-23T10:00:00", - "2023-04-23T12:00:00", - "2023-04-23T14:00:00", - "2023-04-23T16:00:00", - "2023-04-23T18:00:00", - "2023-04-23T20:00:00", - "2023-04-23T22:00:00", - "2023-04-24T00:00:00", - "2023-04-24T02:00:00", - "2023-04-24T04:00:00", - "2023-04-24T06:00:00", - "2023-04-24T08:00:00", - "2023-04-24T10:00:00", - "2023-04-24T12:00:00", - "2023-04-24T14:00:00", - "2023-04-24T16:00:00", - "2023-04-24T18:00:00", - "2023-04-24T20:00:00", - "2023-04-24T22:00:00", - "2023-04-25T00:00:00", - "2023-04-25T02:00:00", - "2023-04-25T04:00:00", - "2023-04-25T06:00:00", - "2023-04-25T08:00:00", - "2023-04-25T10:00:00", - "2023-04-25T12:00:00", - "2023-04-25T14:00:00", - "2023-04-25T16:00:00", - "2023-04-25T18:00:00", - "2023-04-25T20:00:00", - "2023-04-25T22:00:00", - "2023-04-26T00:00:00", - "2023-04-26T02:00:00", - "2023-04-26T04:00:00", - "2023-04-26T06:00:00", - "2023-04-26T08:00:00", - "2023-04-26T10:00:00", - "2023-04-26T12:00:00", - "2023-04-26T14:00:00", - "2023-04-26T16:00:00", - "2023-04-26T18:00:00", - "2023-04-26T20:00:00", - "2023-04-26T22:00:00", - "2023-04-27T00:00:00", - "2023-04-27T02:00:00", - "2023-04-27T04:00:00", - "2023-04-27T06:00:00", - "2023-04-27T08:00:00", - "2023-04-27T10:00:00", - "2023-04-27T12:00:00", - "2023-04-27T14:00:00", - "2023-04-27T16:00:00", - "2023-04-27T18:00:00", - "2023-04-27T20:00:00", - "2023-04-27T22:00:00", - "2023-04-28T00:00:00", - "2023-04-28T02:00:00", - "2023-04-28T04:00:00", - "2023-04-28T06:00:00", - "2023-04-28T08:00:00", - "2023-04-28T10:00:00", - "2023-04-28T12:00:00", - "2023-04-28T14:00:00", - "2023-04-28T16:00:00", - "2023-04-28T18:00:00", - "2023-04-28T20:00:00", - "2023-04-28T22:00:00", - "2023-04-29T00:00:00", - "2023-04-29T02:00:00", - "2023-04-29T04:00:00", - "2023-04-29T06:00:00", - "2023-04-29T08:00:00", - "2023-04-29T10:00:00", - "2023-04-29T12:00:00", - "2023-04-29T14:00:00", - "2023-04-29T16:00:00", - "2023-04-29T18:00:00", - "2023-04-29T20:00:00", - "2023-04-29T22:00:00", - "2023-04-30T00:00:00", - "2023-04-30T02:00:00", - "2023-04-30T04:00:00", - "2023-04-30T06:00:00", - "2023-04-30T08:00:00", - "2023-04-30T10:00:00", - "2023-04-30T12:00:00", - "2023-04-30T14:00:00", - "2023-04-30T16:00:00", - "2023-04-30T18:00:00", - "2023-04-30T20:00:00", - "2023-04-30T22:00:00", - "2023-05-01T00:00:00", - "2023-05-01T02:00:00", - "2023-05-01T04:00:00", - "2023-05-01T06:00:00", - "2023-05-01T08:00:00", - "2023-05-01T10:00:00", - "2023-05-01T12:00:00", - "2023-05-01T14:00:00", - "2023-05-01T16:00:00", - "2023-05-01T18:00:00", - "2023-05-01T20:00:00", - "2023-05-01T22:00:00", - "2023-05-02T00:00:00", - "2023-05-02T02:00:00", - "2023-05-02T04:00:00", - "2023-05-02T06:00:00", - "2023-05-02T08:00:00", - "2023-05-02T10:00:00", - "2023-05-02T12:00:00", - "2023-05-02T14:00:00", - "2023-05-02T16:00:00", - "2023-05-02T18:00:00", - "2023-05-02T20:00:00", - "2023-05-02T22:00:00", - "2023-05-03T00:00:00", - "2023-05-03T02:00:00", - "2023-05-03T04:00:00", - "2023-05-03T06:00:00", - "2023-05-03T08:00:00", - "2023-05-03T10:00:00", - "2023-05-03T12:00:00", - "2023-05-03T14:00:00", - "2023-05-03T16:00:00", - "2023-05-03T18:00:00", - "2023-05-03T20:00:00", - "2023-05-03T22:00:00", - "2023-05-04T00:00:00", - "2023-05-04T02:00:00", - "2023-05-04T04:00:00", - "2023-05-04T06:00:00", - "2023-05-04T08:00:00", - "2023-05-04T10:00:00", - "2023-05-04T12:00:00", - "2023-05-04T14:00:00", - "2023-05-04T16:00:00", - "2023-05-04T18:00:00", - "2023-05-04T20:00:00", - "2023-05-04T22:00:00", - "2023-05-05T00:00:00", - "2023-05-05T02:00:00", - "2023-05-05T04:00:00", - "2023-05-05T06:00:00", - "2023-05-05T08:00:00", - "2023-05-05T10:00:00", - "2023-05-05T12:00:00", - "2023-05-05T14:00:00", - "2023-05-05T16:00:00", - "2023-05-05T18:00:00", - "2023-05-05T20:00:00", - "2023-05-05T22:00:00", - "2023-05-06T00:00:00", - "2023-05-06T02:00:00", - "2023-05-06T04:00:00", - "2023-05-06T06:00:00", - "2023-05-06T08:00:00", - "2023-05-06T10:00:00", - "2023-05-06T12:00:00", - "2023-05-06T14:00:00", - "2023-05-06T16:00:00", - "2023-05-06T18:00:00", - "2023-05-06T20:00:00", - "2023-05-06T22:00:00", - "2023-05-07T00:00:00", - "2023-05-07T02:00:00", - "2023-05-07T04:00:00", - "2023-05-07T06:00:00", - "2023-05-07T08:00:00", - "2023-05-07T10:00:00", - "2023-05-07T12:00:00", - "2023-05-07T14:00:00", - "2023-05-07T16:00:00", - "2023-05-07T18:00:00", - "2023-05-07T20:00:00", - "2023-05-07T22:00:00", - "2023-05-08T00:00:00", - "2023-05-08T02:00:00", - "2023-05-08T04:00:00", - "2023-05-08T06:00:00", - "2023-05-08T08:00:00", - "2023-05-08T10:00:00", - "2023-05-08T12:00:00", - "2023-05-08T14:00:00", - "2023-05-08T16:00:00", - "2023-05-08T18:00:00", - "2023-05-08T20:00:00", - "2023-05-08T22:00:00", - "2023-05-09T00:00:00", - "2023-05-09T02:00:00", - "2023-05-09T04:00:00", - "2023-05-09T06:00:00", - "2023-05-09T08:00:00", - "2023-05-09T10:00:00", - "2023-05-09T12:00:00", - "2023-05-09T14:00:00", - "2023-05-09T16:00:00", - "2023-05-09T18:00:00", - "2023-05-09T20:00:00", - "2023-05-09T22:00:00", - "2023-05-10T00:00:00", - "2023-05-10T02:00:00", - "2023-05-10T04:00:00", - "2023-05-10T06:00:00", - "2023-05-10T08:00:00", - "2023-05-10T10:00:00", - "2023-05-10T12:00:00", - "2023-05-10T14:00:00", - "2023-05-10T16:00:00", - "2023-05-10T18:00:00", - "2023-05-10T20:00:00", - "2023-05-10T22:00:00", - "2023-05-11T00:00:00", - "2023-05-11T02:00:00", - "2023-05-11T04:00:00", - "2023-05-11T06:00:00", - "2023-05-11T08:00:00", - "2023-05-11T10:00:00", - "2023-05-11T12:00:00", - "2023-05-11T14:00:00", - "2023-05-11T16:00:00", - "2023-05-11T18:00:00", - "2023-05-11T20:00:00", - "2023-05-11T22:00:00", - "2023-05-12T00:00:00", - "2023-05-12T02:00:00", - "2023-05-12T04:00:00", - "2023-05-12T06:00:00", - "2023-05-12T08:00:00", - "2023-05-12T10:00:00", - "2023-05-12T12:00:00", - "2023-05-12T14:00:00", - "2023-05-12T16:00:00", - "2023-05-12T18:00:00", - "2023-05-12T20:00:00", - "2023-05-12T22:00:00", - "2023-05-13T00:00:00", - "2023-05-13T02:00:00", - "2023-05-13T04:00:00", - "2023-05-13T06:00:00", - "2023-05-13T08:00:00", - "2023-05-13T10:00:00", - "2023-05-13T12:00:00", - "2023-05-13T14:00:00", - "2023-05-13T16:00:00", - "2023-05-13T18:00:00", - "2023-05-13T20:00:00", - "2023-05-13T22:00:00", - "2023-05-14T00:00:00", - "2023-05-14T02:00:00", - "2023-05-14T04:00:00", - "2023-05-14T06:00:00", - "2023-05-14T08:00:00", - "2023-05-14T10:00:00", - "2023-05-14T12:00:00", - "2023-05-14T14:00:00", - "2023-05-14T16:00:00", - "2023-05-14T18:00:00", - "2023-05-14T20:00:00", - "2023-05-14T22:00:00", - "2023-05-15T00:00:00", - "2023-05-15T02:00:00", - "2023-05-15T04:00:00", - "2023-05-15T06:00:00", - "2023-05-15T08:00:00", - "2023-05-15T10:00:00", - "2023-05-15T12:00:00", - "2023-05-15T14:00:00", - "2023-05-15T16:00:00", - "2023-05-15T18:00:00", - "2023-05-15T20:00:00", - "2023-05-15T22:00:00", - "2023-05-16T00:00:00", - "2023-05-16T02:00:00", - "2023-05-16T04:00:00", - "2023-05-16T06:00:00", - "2023-05-16T08:00:00", - "2023-05-16T10:00:00", - "2023-05-16T12:00:00", - "2023-05-16T14:00:00", - "2023-05-16T16:00:00", - "2023-05-16T18:00:00", - "2023-05-16T20:00:00", - "2023-05-16T22:00:00", - "2023-05-17T00:00:00", - "2023-05-17T02:00:00", - "2023-05-17T04:00:00", - "2023-05-17T06:00:00", - "2023-05-17T08:00:00", - "2023-05-17T10:00:00", - "2023-05-17T12:00:00", - "2023-05-17T14:00:00", - "2023-05-17T16:00:00", - "2023-05-17T18:00:00", - "2023-05-17T20:00:00", - "2023-05-17T22:00:00", - "2023-05-18T00:00:00", - "2023-05-18T02:00:00", - "2023-05-18T04:00:00", - "2023-05-18T06:00:00", - "2023-05-18T08:00:00", - "2023-05-18T10:00:00", - "2023-05-18T12:00:00", - "2023-05-18T14:00:00", - "2023-05-18T16:00:00", - "2023-05-18T18:00:00", - "2023-05-18T20:00:00", - "2023-05-18T22:00:00", - "2023-05-19T00:00:00", - "2023-05-19T02:00:00", - "2023-05-19T04:00:00", - "2023-05-19T06:00:00", - "2023-05-19T08:00:00", - "2023-05-19T10:00:00", - "2023-05-19T12:00:00", - "2023-05-19T14:00:00", - "2023-05-19T16:00:00", - "2023-05-19T18:00:00", - "2023-05-19T20:00:00", - "2023-05-19T22:00:00", - "2023-05-20T00:00:00", - "2023-05-20T02:00:00", - "2023-05-20T04:00:00", - "2023-05-20T06:00:00", - "2023-05-20T08:00:00", - "2023-05-20T10:00:00", - "2023-05-20T12:00:00", - "2023-05-20T14:00:00", - "2023-05-20T16:00:00", - "2023-05-20T18:00:00", - "2023-05-20T20:00:00", - "2023-05-20T22:00:00", - "2023-05-21T00:00:00", - "2023-05-21T02:00:00", - "2023-05-21T04:00:00", - "2023-05-21T06:00:00", - "2023-05-21T08:00:00", - "2023-05-21T10:00:00", - "2023-05-21T12:00:00", - "2023-05-21T14:00:00", - "2023-05-21T16:00:00", - "2023-05-21T18:00:00", - "2023-05-21T20:00:00", - "2023-05-21T22:00:00", - "2023-05-22T00:00:00", - "2023-05-22T02:00:00", - "2023-05-22T04:00:00", - "2023-05-22T06:00:00", - "2023-05-22T08:00:00", - "2023-05-22T10:00:00", - "2023-05-22T12:00:00", - "2023-05-22T14:00:00", - "2023-05-22T16:00:00", - "2023-05-22T18:00:00", - "2023-05-22T20:00:00", - "2023-05-22T22:00:00", - "2023-05-23T00:00:00", - "2023-05-23T02:00:00", - "2023-05-23T04:00:00", - "2023-05-23T06:00:00", - "2023-05-23T08:00:00", - "2023-05-23T10:00:00", - "2023-05-23T12:00:00", - "2023-05-23T14:00:00", - "2023-05-23T16:00:00", - "2023-05-23T18:00:00", - "2023-05-23T20:00:00", - "2023-05-23T22:00:00", - "2023-05-24T00:00:00", - "2023-05-24T02:00:00", - "2023-05-24T04:00:00", - "2023-05-24T06:00:00", - "2023-05-24T08:00:00", - "2023-05-24T10:00:00", - "2023-05-24T12:00:00", - "2023-05-24T14:00:00", - "2023-05-24T16:00:00", - "2023-05-24T18:00:00", - "2023-05-24T20:00:00", - "2023-05-24T22:00:00", - "2023-05-25T00:00:00", - "2023-05-25T02:00:00", - "2023-05-25T04:00:00", - "2023-05-25T06:00:00", - "2023-05-25T08:00:00", - "2023-05-25T10:00:00", - "2023-05-25T12:00:00", - "2023-05-25T14:00:00", - "2023-05-25T16:00:00", - "2023-05-25T18:00:00", - "2023-05-25T20:00:00", - "2023-05-25T22:00:00", - "2023-05-26T00:00:00", - "2023-05-26T02:00:00", - "2023-05-26T04:00:00", - "2023-05-26T06:00:00", - "2023-05-26T08:00:00", - "2023-05-26T10:00:00", - "2023-05-26T12:00:00", - "2023-05-26T14:00:00", - "2023-05-26T16:00:00", - "2023-05-26T18:00:00", - "2023-05-26T20:00:00", - "2023-05-26T22:00:00", - "2023-05-27T00:00:00", - "2023-05-27T02:00:00", - "2023-05-27T04:00:00", - "2023-05-27T06:00:00", - "2023-05-27T08:00:00", - "2023-05-27T10:00:00", - "2023-05-27T12:00:00", - "2023-05-27T14:00:00", - "2023-05-27T16:00:00", - "2023-05-27T18:00:00", - "2023-05-27T20:00:00", - "2023-05-27T22:00:00", - "2023-05-28T00:00:00", - "2023-05-28T02:00:00", - "2023-05-28T04:00:00", - "2023-05-28T06:00:00", - "2023-05-28T08:00:00", - "2023-05-28T10:00:00", - "2023-05-28T12:00:00", - "2023-05-28T14:00:00", - "2023-05-28T16:00:00", - "2023-05-28T18:00:00", - "2023-05-28T20:00:00", - "2023-05-28T22:00:00", - "2023-05-29T00:00:00", - "2023-05-29T02:00:00", - "2023-05-29T04:00:00", - "2023-05-29T06:00:00", - "2023-05-29T08:00:00", - "2023-05-29T10:00:00", - "2023-05-29T12:00:00", - "2023-05-29T14:00:00", - "2023-05-29T16:00:00", - "2023-05-29T18:00:00", - "2023-05-29T20:00:00", - "2023-05-29T22:00:00", - "2023-05-30T00:00:00", - "2023-05-30T02:00:00", - "2023-05-30T04:00:00", - "2023-05-30T06:00:00", - "2023-05-30T08:00:00", - "2023-05-30T10:00:00", - "2023-05-30T12:00:00", - "2023-05-30T14:00:00", - "2023-05-30T16:00:00", - "2023-05-30T18:00:00", - "2023-05-30T20:00:00", - "2023-05-30T22:00:00", - "2023-05-31T00:00:00", - "2023-05-31T02:00:00", - "2023-05-31T04:00:00", - "2023-05-31T06:00:00", - "2023-05-31T08:00:00", - "2023-05-31T10:00:00", - "2023-05-31T12:00:00", - "2023-05-31T14:00:00", - "2023-05-31T16:00:00", - "2023-05-31T18:00:00", - "2023-05-31T20:00:00", - "2023-05-31T22:00:00", - "2023-06-01T00:00:00" - ], - "y": [ - 15603.0, - 15748.0, - 15831.0, - 15832.0, - 15801.0, - 15795.0, - 15801.0, - 15906.0, - 15839.0, - 15898.0, - 15877.0, - 15906.0, - 15868.0, - 15858.0, - 15839.0, - 15818.0, - 15901.0, - 15916.0, - 15846.0, - 15870.0, - 15867.0, - 15774.0, - 15837.0, - 15860.0, - 15820.0, - 15832.0, - 15811.0, - 15805.0, - 15828.0, - 15857.0, - 15793.0, - 15692.0, - 15678.0, - 15734.0, - 15843.0, - 15861.0, - 15825.0, - 15859.0, - 15857.0, - 15850.0, - 15866.0, - 15873.0, - 15883.0, - 15863.0, - 15865.0, - 15869.0, - 15839.0, - 15773.0, - 15832.0, - 15847.0, - 15814.0, - 15854.0, - 15854.0, - 15818.0, - 15809.0, - 15845.0, - 15848.0, - 15847.0, - 15834.0, - 15822.0, - 15826.0, - 15823.0, - 15843.0, - 15834.0, - 15854.0, - 15820.0, - 15830.0, - 15819.0, - 15815.0, - 15805.0, - 15842.0, - 15831.0, - 15879.0, - 15912.0, - 15848.0, - 15827.0, - 15837.0, - 15866.0, - 15842.0, - 15811.0, - 15837.0, - 15836.0, - 15815.0, - 15897.0, - 15820.0, - 15824.0, - 15802.0, - 15821.0, - 15807.0, - 15786.0, - 15823.0, - 15755.0, - 15675.0, - 15678.0, - 15700.0, - 15707.0, - 15673.0, - 15658.0, - 15592.0, - 15637.0, - 15631.0, - 15650.0, - 15619.0, - 15585.0, - 15647.0, - 15622.0, - 15550.0, - 15569.0, - 15544.0, - 15560.0, - 15606.0, - 15579.0, - 15578.0, - 15599.0, - 15578.0, - 15561.0, - 15532.0, - 15562.0, - 15565.0, - 15580.0, - 15585.0, - 15581.0, - 15529.0, - 15473.0, - 15501.0, - 15452.0, - 15438.0, - 15523.0, - 15415.0, - 15454.0, - 15497.0, - 15518.0, - 15479.0, - 15451.0, - 15452.0, - 15476.0, - 15445.0, - 15461.0, - 15510.0, - 15504.0, - 15489.0, - 15470.0, - 15444.0, - 15439.0, - 15437.0, - 15433.0, - 15425.0, - 15426.0, - 15439.0, - 15465.0, - 15462.0, - 15442.0, - 15466.0, - 15504.0, - 15516.0, - 15533.0, - 15483.0, - 15569.0, - 15549.0, - 15661.0, - 15653.0, - 15660.0, - 15629.0, - 15692.0, - 15666.0, - 15682.0, - 15682.0, - 15605.0, - 15639.0, - 15621.0, - 15637.0, - 15710.0, - 15859.0, - 15874.0, - 15833.0, - 15790.0, - 15763.0, - 15729.0, - 15770.0, - 15784.0, - 15803.0, - 15929.0, - 15921.0, - 15897.0, - 15869.0, - 15869.0, - 15856.0, - 15889.0, - 15936.0, - 15872.0, - 15849.0, - 15896.0, - 15838.0, - 15866.0, - 15863.0, - 15847.0, - 15797.0, - 15848.0, - 15895.0, - 16001.0, - 15970.0, - 16001.0, - 15992.0, - 16001.0, - 15975.0, - 15991.0, - 15988.0, - 15966.0, - 15953.0, - 15944.0, - 15887.0, - 15871.0, - 15823.0, - 15888.0, - 15891.0, - 15924.0, - 15926.0, - 15922.0, - 15910.0, - 15929.0, - 15888.0, - 15887.0, - 15901.0, - 15926.0, - 15919.0, - 15910.0, - 15904.0, - 15911.0, - 15912.0, - 15921.0, - 15923.0, - 15904.0, - 15911.0, - 15910.0, - 15883.0, - 15962.0, - 15883.0, - 15879.0, - 15918.0, - 16069.0, - 16114.0, - 16126.0, - 16091.0, - 16114.0, - 16156.0, - 16148.0, - 16070.0, - 16073.0, - 16150.0, - 16101.0, - 16016.0, - 16017.0, - 16027.0, - 16035.0, - 16043.0, - 16010.0, - 16050.0, - 16065.0, - 16074.0, - 16144.0, - 16135.0, - 16222.0, - 16287.0, - 16235.0, - 16282.0, - 16240.0, - 16171.0, - 16238.0, - 16245.0, - 16230.0, - 16210.0, - 16124.0, - 16171.0, - 16299.0, - 16307.0, - 16635.0, - 16923.0, - 16922.0, - 16795.0, - 16836.0, - 16875.0, - 16914.0, - 16896.0, - 16736.0, - 17360.0, - 17430.0, - 17350.0, - 17374.0, - 17297.0, - 17389.0, - 17342.0, - 17347.0, - 17462.0, - 17515.0, - 17482.0, - 17816.0, - 17800.0, - 17880.0, - 18281.0, - 18384.0, - 19320.0, - 19309.0, - 19211.0, - 19301.0, - 18935.0, - 19122.0, - 19328.0, - 19164.0, - 19137.0, - 19170.0, - 19260.0, - 19347.0, - 19157.0, - 19081.0, - 19129.0, - 19092.0, - 19136.0, - 19102.0, - 19068.0, - 19281.0, - 19301.0, - 19243.0, - 19301.0, - 19277.0, - 19496.0, - 19412.0, - 19533.0, - 19500.0, - 19244.0, - 19237.0, - 19251.0, - 19381.0, - 19524.0, - 19700.0, - 19510.0, - 19566.0, - 19466.0, - 19529.0, - 19541.0, - 19536.0, - 19595.0, - 19579.0, - 19606.0, - 19628.0, - 19694.0, - 19763.0, - 19751.0, - 19610.0, - 19675.0, - 19759.0, - 19783.0, - 19696.0, - 19548.0, - 19616.0, - 19722.0, - 19436.0, - 19338.0, - 19347.0, - 19225.0, - 19133.0, - 19171.0, - 19185.0, - 19278.0, - 19224.0, - 19205.0, - 19131.0, - 19233.0, - 19337.0, - 19265.0, - 19488.0, - 19338.0, - 19447.0, - 19499.0, - 19465.0, - 19367.0, - 19339.0, - 19365.0, - 19340.0, - 19487.0, - 19540.0, - 19678.0, - 19797.0, - 20531.0, - 20858.0, - 20779.0, - 20757.0, - 20790.0, - 20848.0, - 21235.0, - 21113.0, - 21118.0, - 21186.0, - 21426.0, - 21469.0, - 21376.0, - 20990.0, - 21030.0, - 20981.0, - 21108.0, - 21128.0, - 21088.0, - 20985.0, - 21121.0, - 20997.0, - 21131.0, - 20936.0, - 20794.0, - 20912.0, - 20868.0, - 20793.0, - 20876.0, - 20813.0, - 20884.0, - 21062.0, - 21005.0, - 21046.0, - 21230.0, - 21003.0, - 21145.0, - 21078.0, - 21242.0, - 21234.0, - 21262.0, - 21160.0, - 21053.0, - 21089.0, - 21054.0, - 21068.0, - 21032.0, - 21147.0, - 21054.0, - 20799.0, - 20665.0, - 20764.0, - 20817.0, - 20897.0, - 20777.0, - 20784.0, - 20771.0, - 20693.0, - 20707.0, - 20854.0, - 21615.0, - 21126.0, - 21217.0, - 21176.0, - 21226.0, - 21045.0, - 21070.0, - 21108.0, - 21216.0, - 21151.0, - 21136.0, - 21173.0, - 21196.0, - 21112.0, - 20772.0, - 20957.0, - 21162.0, - 21206.0, - 21083.0, - 21110.0, - 21078.0, - 21188.0, - 21365.0, - 21389.0, - 21248.0, - 21257.0, - 21241.0, - 21304.0, - 21243.0, - 21182.0, - 21160.0, - 21168.0, - 21169.0, - 21194.0, - 21176.0, - 21224.0, - 21172.0, - 21212.0, - 21380.0, - 21337.0, - 21400.0, - 21369.0, - 21421.0, - 21540.0, - 21665.0, - 21628.0, - 21693.0, - 21988.0, - 21913.0, - 21840.0, - 21766.0, - 21810.0, - 21835.0, - 21785.0, - 21251.0, - 21191.0, - 21176.0, - 21341.0, - 21296.0, - 21000.0, - 20977.0, - 21067.0, - 21050.0, - 21062.0, - 21000.0, - 21206.0, - 21138.0, - 21112.0, - 21308.0, - 21303.0, - 21318.0, - 21317.0, - 21132.0, - 21311.0, - 21282.0, - 21293.0, - 21270.0, - 21213.0, - 21141.0, - 21172.0, - 21180.0, - 21058.0, - 21092.0, - 21321.0, - 21550.0, - 21550.0, - 21752.0, - 21632.0, - 21611.0, - 21625.0, - 21646.0, - 21646.0, - 21714.0, - 21812.0, - 21909.0, - 21827.0, - 21507.0, - 21532.0, - 21638.0, - 21600.0, - 21578.0, - 21532.0, - 21445.0, - 21554.0, - 21553.0, - 21730.0, - 21731.0, - 21562.0, - 21671.0, - 21687.0, - 21659.0, - 21600.0, - 21601.0, - 21622.0, - 21634.0, - 21655.0, - 21758.0, - 21695.0, - 21729.0, - 21711.0, - 21671.0, - 21626.0, - 21616.0, - 21623.0, - 21656.0, - 21680.0, - 21661.0, - 21654.0, - 21491.0, - 21398.0, - 21169.0, - 21216.0, - 21248.0, - 21269.0, - 21301.0, - 21229.0, - 21099.0, - 21250.0, - 21227.0, - 21239.0, - 21209.0, - 21459.0, - 21499.0, - 21453.0, - 21369.0, - 21216.0, - 21248.0, - 21298.0, - 21341.0, - 21354.0, - 21481.0, - 21462.0, - 21495.0, - 21400.0, - 21616.0, - 21546.0, - 21604.0, - 21661.0, - 21673.0, - 21670.0, - 21630.0, - 21576.0, - 21552.0, - 21570.0, - 21525.0, - 21332.0, - 21435.0, - 21339.0, - 21425.0, - 21445.0, - 21378.0, - 20992.0, - 21073.0, - 21137.0, - 21130.0, - 21081.0, - 21059.0, - 21012.0, - 20974.0, - 20522.0, - 20348.0, - 20303.0, - 20406.0, - 20349.0, - 20329.0, - 20400.0, - 20400.0, - 20318.0, - 20371.0, - 20296.0, - 20346.0, - 20392.0, - 20180.0, - 20275.0, - 20275.0, - 20320.0, - 20318.0, - 20333.0, - 20334.0, - 20334.0, - 20392.0, - 20350.0, - 20322.0, - 20312.0, - 20468.0, - 20505.0, - 20446.0, - 20409.0, - 20437.0, - 20428.0, - 20551.0, - 20522.0, - 20475.0, - 20573.0, - 20623.0, - 20651.0, - 20385.0, - 20421.0, - 20342.0, - 20470.0, - 20471.0, - 20493.0, - 20240.0, - 20231.0, - 20200.0, - 20139.0, - 20040.0, - 20160.0, - 20148.0, - 20302.0, - 20223.0, - 20225.0, - 20251.0, - 20304.0, - 20281.0, - 20294.0, - 20199.0, - 20595.0, - 20547.0, - 20704.0, - 20709.0, - 20690.0, - 20597.0, - 20601.0, - 20668.0, - 20629.0, - 20679.0, - 20950.0, - 21220.0, - 21370.0, - 21370.0, - 21799.0, - 22596.0, - 22714.0, - 22969.0, - 23072.0, - 23014.0, - 22929.0, - 22992.0, - 22947.0, - 22852.0, - 23500.0, - 23268.0, - 23264.0, - 22982.0, - 22043.0, - 22395.0, - 22378.0, - 22286.0, - 22224.0, - 22334.0, - 22393.0, - 22360.0, - 22653.0, - 22754.0, - 22936.0, - 22861.0, - 22970.0, - 23068.0, - 22987.0, - 23049.0, - 22925.0, - 22971.0, - 22913.0, - 22971.0, - 23065.0, - 23082.0, - 23022.0, - 23018.0, - 23029.0, - 23108.0, - 23049.0, - 23127.0, - 22982.0, - 22981.0, - 23083.0, - 23089.0, - 23265.0, - 22780.0, - 22951.0, - 22961.0, - 22724.0, - 22715.0, - 22867.0, - 22921.0, - 22905.0, - 23265.0, - 23277.0, - 23242.0, - 23315.0, - 23221.0, - 23243.0, - 23186.0, - 23250.0, - 23299.0, - 23369.0, - 23363.0, - 23436.0, - 23261.0, - 23066.0, - 23075.0, - 23000.0, - 23156.0, - 23126.0, - 22748.0, - 22974.0, - 22715.0, - 22721.0, - 22634.0, - 22582.0, - 22674.0, - 22746.0, - 22669.0, - 22338.0, - 22352.0, - 22463.0, - 22440.0, - 22816.0, - 22787.0, - 23028.0, - 22950.0, - 22936.0, - 22929.0, - 22467.0, - 22644.0, - 22590.0, - 22537.0, - 22650.0, - 22556.0, - 22602.0, - 22652.0, - 22628.0, - 22573.0, - 22530.0, - 22543.0, - 22586.0, - 22526.0, - 22176.0, - 21906.0, - 21965.0, - 21936.0, - 22009.0, - 22016.0, - 21904.0, - 21881.0, - 21943.0, - 21899.0, - 21801.0, - 21812.0, - 21857.0, - 21875.0, - 21808.0, - 21822.0, - 22067.0, - 21989.0, - 22103.0, - 22000.0, - 22046.0, - 22178.0, - 22130.0, - 22082.0, - 22146.0, - 22085.0, - 22362.0, - 22389.0, - 22335.0, - 22333.0, - 22334.0, - 22194.0, - 22209.0, - 22143.0, - 22198.0, - 22416.0, - 22247.0, - 22034.0, - 21967.0, - 22035.0, - 22142.0, - 22153.0, - 22167.0, - 22104.0, - 21937.0, - 21953.0, - 22068.0, - 22030.0, - 22180.0, - 22181.0, - 21991.0, - 21895.0, - 21885.0, - 21986.0, - 22143.0, - 22357.0, - 22269.0, - 22330.0, - 22249.0, - 22205.0, - 22210.0, - 22237.0, - 21929.0, - 22089.0, - 22163.0, - 22089.0, - 22090.0, - 22036.0, - 21980.0, - 22042.0, - 22030.0, - 22028.0, - 22010.0, - 21992.0, - 22135.0, - 22102.0, - 22115.0, - 20961.0, - 21075.0, - 21086.0, - 21121.0, - 21104.0, - 21076.0, - 21116.0, - 21098.0, - 21104.0, - 21035.0, - 20917.0, - 21031.0, - 21026.0, - 21039.0, - 21017.0, - 21034.0, - 21007.0, - 21043.0, - 21047.0, - 20998.0, - 20979.0, - 20931.0, - 20864.0, - 21008.0, - 21256.0, - 21051.0, - 21092.0, - 21071.0, - 21052.0, - 21066.0, - 21148.0, - 21124.0, - 21094.0, - 21093.0, - 21171.0, - 21126.0, - 21073.0, - 21046.0, - 21009.0, - 21030.0, - 21079.0, - 21010.0, - 21056.0, - 21139.0, - 21049.0, - 20971.0, - 20972.0, - 20962.0, - 21051.0, - 21000.0, - 21022.0, - 20977.0, - 20996.0, - 20965.0, - 20996.0, - 21065.0, - 21112.0, - 20968.0, - 20913.0, - 21042.0, - 21019.0, - 21024.0, - 20849.0, - 20812.0, - 20887.0, - 20948.0, - 20859.0, - 20983.0, - 20895.0, - 20853.0, - 20845.0, - 20587.0, - 20593.0, - 20641.0, - 20577.0, - 20520.0, - 20475.0, - 20482.0, - 20518.0, - 20446.0, - 20269.0, - 19683.0, - 19145.0, - 19257.0, - 18984.0, - 18946.0, - 18903.0, - 18843.0, - 18779.0, - 18673.0, - 19026.0, - 18760.0, - 18697.0, - 18844.0, - 18902.0, - 18989.0, - 19567.0, - 19342.0, - 19277.0, - 18786.0, - 18949.0, - 19034.0, - 18942.0, - 18888.0, - 19017.0, - 19050.0, - 19155.0, - 19219.0, - 19217.0, - 19181.0, - 19200.0, - 19195.0, - 19216.0, - 19237.0, - 19209.0, - 19257.0, - 19638.0, - 19577.0, - 19974.0, - 20665.0, - 20993.0, - 20775.0, - 20880.0, - 20891.0, - 20562.0, - 20616.0, - 21000.0, - 22323.0, - 22407.0, - 22505.0, - 22192.0, - 22338.0, - 22600.0, - 22678.0, - 22547.0, - 22488.0, - 22613.0, - 23072.0, - 23972.0, - 24160.0, - 23810.0, - 23275.0, - 22855.0, - 23000.0, - 23137.0, - 22987.0, - 23006.0, - 23194.0, - 22994.0, - 23422.0, - 23514.0, - 23244.0, - 23036.0, - 23052.0, - 23163.0, - 22973.0, - 22982.0, - 22945.0, - 23000.0, - 23212.0, - 23257.0, - 23469.0, - 23433.0, - 23475.0, - 23324.0, - 23590.0, - 23523.0, - 23605.0, - 24196.0, - 24237.0, - 24200.0, - 24511.0, - 24721.0, - 25360.0, - 24911.0, - 24863.0, - 24884.0, - 25192.0, - 25426.0, - 25656.0, - 25591.0, - 25620.0, - 25879.0, - 25699.0, - 25708.0, - 25765.0, - 25732.0, - 25518.0, - 25591.0, - 25678.0, - 25568.0, - 25245.0, - 25419.0, - 25539.0, - 25411.0, - 25280.0, - 25350.0, - 25492.0, - 25506.0, - 25854.0, - 26222.0, - 26549.0, - 26259.0, - 26264.0, - 25987.0, - 25661.0, - 25882.0, - 26519.0, - 26484.0, - 26441.0, - 26025.0, - 25908.0, - 25882.0, - 25967.0, - 26166.0, - 25952.0, - 26091.0, - 25968.0, - 26048.0, - 25756.0, - 26098.0, - 26103.0, - 26100.0, - 26100.0, - 26055.0, - 26200.0, - 26083.0, - 26173.0, - 26158.0, - 26200.0, - 26279.0, - 26160.0, - 26119.0, - 26132.0, - 26340.0, - 26535.0, - 26451.0, - 24551.0, - 25010.0, - 25158.0, - 25005.0, - 25207.0, - 25443.0, - 25420.0, - 25450.0, - 25447.0, - 25207.0, - 26296.0, - 26075.0, - 26216.0, - 26042.0, - 26145.0, - 26121.0, - 26102.0, - 26157.0, - 26130.0, - 26149.0, - 25769.0, - 25967.0, - 26064.0, - 25672.0, - 25921.0, - 25508.0, - 25596.0, - 25615.0, - 25686.0, - 25588.0, - 25622.0, - 25596.0, - 25581.0, - 25679.0, - 25719.0, - 25604.0, - 25323.0, - 25562.0, - 25570.0, - 25708.0, - 25618.0, - 25631.0, - 25619.0, - 25740.0, - 25940.0, - 26165.0, - 25902.0, - 25922.0, - 25908.0, - 25911.0, - 25968.0, - 25864.0, - 25901.0, - 25725.0, - 25873.0, - 25956.0, - 25886.0, - 25697.0, - 24973.0, - 25000.0, - 25003.0, - 25180.0, - 25140.0, - 25062.0, - 24966.0, - 24972.0, - 24920.0, - 24880.0, - 25012.0, - 24832.0, - 24911.0, - 24798.0, - 25268.0, - 25063.0, - 25187.0, - 25230.0, - 25256.0, - 25449.0, - 25892.0, - 26044.0, - 26073.0, - 26175.0, - 26240.0, - 26018.0, - 26198.0, - 26089.0, - 26155.0, - 26185.0, - 26256.0, - 26394.0, - 26493.0, - 26341.0, - 26343.0, - 26122.0, - 25999.0, - 25579.0, - 25686.0, - 25775.0, - 25760.0, - 25833.0, - 25871.0, - 25757.0, - 25510.0, - 25596.0, - 25714.0, - 26017.0, - 26194.0, - 26113.0, - 26237.0, - 26269.0, - 26233.0, - 26388.0, - 26302.0, - 26327.0, - 26207.0, - 26231.0, - 26200.0, - 26205.0, - 26212.0, - 26192.0, - 26161.0, - 26296.0, - 26234.0, - 26211.0, - 26245.0, - 26259.0, - 26195.0, - 26216.0, - 26156.0, - 26103.0, - 25979.0, - 26061.0, - 25837.0, - 25868.0, - 26082.0, - 25668.0, - 25756.0, - 25701.0, - 25796.0, - 26128.0, - 25984.0, - 25986.0, - 25810.0, - 25745.0, - 25793.0, - 25448.0, - 25507.0, - 25520.0, - 25656.0, - 25644.0, - 25662.0, - 25828.0, - 25942.0, - 25753.0, - 25622.0, - 25790.0, - 25746.0, - 25782.0, - 25711.0, - 26066.0, - 26029.0, - 26100.0, - 26041.0, - 26044.0, - 26108.0, - 25987.0, - 25718.0, - 25755.0, - 25926.0, - 25856.0, - 25857.0, - 25780.0, - 25855.0, - 25766.0, - 25615.0, - 25593.0, - 25630.0, - 25624.0, - 25717.0, - 25674.0, - 25714.0, - 25678.0, - 25715.0, - 25735.0, - 25722.0, - 25672.0, - 25535.0, - 25571.0, - 25534.0, - 25673.0, - 25662.0, - 25632.0, - 25622.0, - 25600.0, - 25629.0, - 25638.0, - 25731.0, - 25818.0, - 25812.0, - 25741.0, - 25754.0, - 25742.0, - 25722.0, - 25689.0, - 25634.0, - 25658.0, - 25685.0, - 25821.0, - 25787.0, - 25750.0, - 25650.0, - 25651.0, - 25677.0, - 25665.0, - 25650.0, - 25644.0, - 25811.0, - 26090.0, - 25952.0, - 25970.0, - 25961.0, - 25989.0, - 25967.0, - 25980.0, - 26034.0, - 26011.0, - 26236.0, - 26837.0, - 26853.0, - 26889.0, - 27264.0, - 27668.0, - 27647.0, - 27387.0, - 27615.0, - 27545.0, - 27537.0, - 27687.0, - 27700.0, - 27678.0, - 27620.0, - 27692.0, - 27652.0, - 27649.0, - 27414.0, - 27382.0, - 27452.0, - 27490.0, - 27503.0, - 27405.0, - 27317.0, - 27316.0, - 27102.0, - 27258.0, - 27179.0, - 27357.0, - 27379.0, - 27419.0, - 27368.0, - 27413.0, - 27410.0, - 27334.0, - 27567.0, - 27580.0, - 27513.0, - 27451.0, - 27541.0, - 27842.0, - 27730.0, - 27847.0, - 27799.0, - 27854.0, - 27905.0, - 27890.0, - 27554.0, - 27586.0, - 27617.0, - 27730.0, - 27706.0, - 27639.0, - 27639.0, - 27696.0, - 27717.0, - 27748.0, - 27665.0, - 27640.0, - 27610.0, - 27570.0, - 27621.0, - 27576.0, - 27577.0, - 27544.0, - 27611.0, - 27652.0, - 27583.0, - 27548.0, - 27541.0, - 27616.0, - 27585.0, - 27594.0, - 27659.0, - 27625.0, - 27336.0, - 27340.0, - 27321.0, - 27134.0, - 27243.0, - 26973.0, - 26894.0, - 26923.0, - 27003.0, - 27039.0, - 27020.0, - 26978.0, - 26955.0, - 26998.0, - 27100.0, - 27212.0, - 27258.0, - 27680.0, - 27715.0, - 27550.0, - 27389.0, - 27583.0, - 27689.0, - 27691.0, - 27575.0, - 27552.0, - 27635.0, - 27457.0, - 26751.0, - 26844.0, - 26801.0, - 26747.0, - 26790.0, - 26703.0, - 26641.0, - 26343.0, - 26409.0, - 26364.0, - 26383.0, - 26358.0, - 26342.0, - 26113.0, - 26275.0, - 25972.0, - 25955.0, - 25644.0, - 25750.0, - 25757.0, - 25734.0, - 25831.0, - 25681.0, - 25693.0, - 25630.0, - 25552.0, - 25657.0, - 25557.0, - 25403.0, - 24834.0, - 24833.0, - 24793.0, - 24831.0, - 24842.0, - 24951.0, - 24860.0, - 24829.0, - 24880.0, - 24895.0, - 25104.0, - 25341.0, - 25250.0, - 25330.0, - 25503.0, - 25182.0, - 25148.0, - 25238.0, - 25399.0, - 25320.0, - 25280.0, - 25267.0, - 25084.0, - 25131.0, - 25092.0, - 25063.0, - 25136.0, - 25267.0, - 25326.0, - 25247.0, - 24979.0, - 24839.0, - 25034.0, - 25067.0, - 24732.0, - 24800.0, - 24810.0, - 24824.0, - 24876.0, - 24792.0, - 24727.0, - 24794.0, - 24712.0, - 24763.0, - 24869.0, - 24859.0, - 24937.0, - 25203.0, - 25197.0, - 25782.0, - 25795.0, - 25844.0, - 25800.0, - 25868.0, - 25763.0, - 26157.0, - 26188.0, - 26910.0, - 26986.0, - 26920.0, - 25365.0, - 25999.0, - 25735.0, - 26034.0, - 26162.0, - 26324.0, - 26180.0, - 26239.0, - 26238.0, - 26273.0, - 26463.0, - 26576.0, - 26888.0, - 26879.0, - 26735.0, - 26777.0, - 26686.0, - 26781.0, - 26792.0, - 26587.0, - 26678.0, - 26404.0, - 26426.0, - 26547.0, - 26617.0, - 26620.0, - 26588.0, - 26576.0, - 26646.0, - 26659.0, - 26657.0, - 26586.0, - 26619.0, - 26701.0, - 26595.0, - 26486.0, - 26561.0, - 26546.0, - 26529.0, - 26425.0, - 26481.0, - 26568.0, - 26579.0, - 26556.0, - 26514.0, - 26580.0, - 26920.0, - 26887.0, - 26667.0, - 26703.0, - 26526.0, - 25946.0, - 25963.0, - 25913.0, - 26039.0, - 25944.0, - 25948.0, - 25865.0, - 25717.0, - 25801.0, - 25397.0, - 25548.0, - 25603.0, - 25503.0, - 25586.0, - 25521.0, - 25538.0, - 25559.0, - 25630.0, - 25583.0, - 25980.0, - 26003.0, - 26062.0, - 26053.0, - 26032.0, - 25883.0, - 25869.0, - 25860.0, - 25981.0, - 25971.0, - 25874.0, - 25732.0, - 25645.0, - 25847.0, - 25656.0, - 26152.0, - 26182.0, - 26226.0, - 26246.0, - 26373.0, - 26262.0, - 26316.0, - 26338.0, - 26102.0, - 26257.0, - 26161.0, - 26241.0, - 26207.0, - 26161.0, - 26548.0, - 26434.0, - 26453.0, - 26344.0, - 26396.0, - 26462.0, - 26565.0, - 26642.0, - 26763.0, - 26817.0, - 26762.0, - 26783.0, - 26837.0, - 26658.0, - 26631.0, - 26673.0, - 26626.0, - 26609.0, - 26316.0, - 26007.0, - 26198.0, - 26253.0, - 26220.0, - 26256.0, - 26236.0, - 26211.0, - 26233.0, - 26308.0, - 26205.0, - 26229.0, - 26275.0, - 26322.0, - 26291.0, - 26322.0, - 26137.0, - 25868.0, - 25639.0, - 25606.0, - 25659.0, - 25382.0, - 25359.0, - 25358.0, - 25264.0, - 25340.0, - 25106.0, - 24889.0, - 25039.0, - 25197.0, - 25263.0, - 25174.0, - 25067.0, - 25221.0, - 25121.0, - 25272.0, - 25223.0, - 25045.0, - 25213.0, - 25280.0, - 25189.0, - 25217.0, - 25265.0, - 25238.0, - 25255.0, - 25137.0, - 25159.0, - 25275.0, - 25666.0, - 25683.0, - 25093.0, - 25231.0, - 25082.0, - 25156.0, - 25094.0, - 25024.0, - 25106.0, - 25130.0, - 25106.0, - 25080.0, - 24931.0, - 24904.0, - 24687.0, - 24600.0, - 24729.0, - 24748.0, - 24606.0, - 24327.0, - 24084.0, - 24127.0, - 24202.0, - 24214.0, - 24309.0, - 24254.0, - 24234.0, - 24397.0, - 24691.0, - 24695.0, - 24743.0, - 24725.0, - 24662.0, - 24689.0, - 24759.0, - 24731.0, - 24741.0, - 24746.0, - 24745.0, - 24782.0, - 24797.0, - 24708.0, - 24614.0, - 24784.0, - 24746.0, - 24748.0, - 24767.0, - 24714.0, - 24817.0, - 24959.0, - 24847.0, - 24777.0, - 24790.0, - 24825.0, - 25053.0, - 25082.0, - 25170.0, - 25208.0, - 25223.0, - 25152.0, - 25175.0, - 25200.0, - 25284.0, - 25209.0, - 25173.0, - 24984.0, - 24835.0, - 24945.0, - 24893.0, - 25048.0, - 24865.0, - 24854.0, - 24810.0, - 24886.0, - 24923.0, - 24802.0, - 24857.0, - 24888.0, - 24991.0, - 24912.0, - 24851.0, - 24781.0, - 24817.0, - 24600.0, - 24623.0, - 24753.0, - 25009.0, - 25263.0, - 25228.0, - 25260.0, - 25182.0, - 25225.0, - 25119.0, - 25317.0, - 25332.0, - 25322.0, - 25285.0, - 25115.0, - 24611.0, - 24827.0, - 24977.0, - 24893.0, - 24909.0, - 24925.0, - 24942.0, - 24921.0, - 24855.0, - 24865.0, - 24903.0, - 24864.0, - 24875.0, - 24849.0, - 24910.0, - 24878.0, - 24870.0, - 24869.0, - 24876.0, - 24864.0, - 24899.0, - 24893.0, - 24889.0, - 24954.0, - 25120.0, - 25044.0, - 25024.0, - 25089.0, - 25114.0, - 25151.0, - 25077.0, - 25022.0, - 25028.0, - 24836.0, - 24873.0, - 24889.0, - 24858.0, - 24901.0, - 24763.0, - 24711.0, - 24589.0, - 24635.0, - 24762.0, - 24845.0, - 24821.0, - 24798.0, - 24977.0, - 24880.0, - 24831.0, - 24820.0, - 24884.0, - 24842.0, - 24970.0, - 25304.0, - 25360.0, - 25258.0, - 25282.0, - 25359.0, - 25253.0, - 25352.0, - 25229.0, - 25251.0, - 25225.0, - 25274.0, - 25203.0, - 24840.0, - 24837.0, - 24742.0, - 24829.0, - 24819.0, - 24579.0, - 24458.0, - 24438.0, - 24407.0, - 24537.0, - 24500.0, - 24311.0, - 24435.0, - 24525.0, - 24399.0, - 24500.0, - 24518.0, - 24589.0, - 24441.0, - 24566.0, - 24675.0, - 24689.0, - 24695.0, - 24615.0, - 24631.0, - 24545.0, - 24674.0, - 24652.0, - 24619.0, - 24734.0, - 24965.0, - 24909.0, - 24954.0, - 24911.0, - 24910.0, - 24894.0, - 24957.0, - 24909.0, - 24926.0, - 24910.0, - 24908.0, - 24844.0, - 24893.0, - 24945.0, - 24933.0, - 24953.0, - 25047.0, - 25238.0, - 25322.0, - 25309.0, - 25346.0, - 25292.0, - 25300.0, - 25301.0, - 25402.0, - 25470.0, - 25627.0, - 25948.0, - 26162.0, - 26245.0, - 26038.0, - 26053.0, - 26006.0, - 26053.0, - 26056.0, - 26035.0, - 25780.0, - 25856.0, - 25800.0, - 25837.0, - 25915.0, - 25893.0, - 25973.0, - 25960.0, - 25951.0, - 26001.0, - 26092.0, - 25921.0, - 25811.0, - 25866.0, - 25956.0, - 25835.0, - 25820.0, - 25904.0, - 25850.0, - 25332.0, - 25452.0, - 25431.0, - 25389.0, - 25380.0, - 25278.0, - 25388.0, - 25292.0, - 25350.0, - 25469.0, - 25358.0 - ], - "type": "scatter", - "xaxis": "x", - "yaxis": "y" - }, - { - "marker": { - "color": "green", - "symbol": "arrow-up" - }, - "mode": "markers", - "name": "Buy", "x": [ "2022-12-20T00:00:00", "2022-12-20T02:00:00", @@ -20612,1975 +6578,8459 @@ "2023-05-31T22:00:00", "2023-06-01T00:00:00" ], - "y": [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 15912.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 15897.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 15629.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 21550.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 21661.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 20679.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 20775.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26296.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26165.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26240.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26108.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25952.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 27689.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25763.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26316.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25208.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25228.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25089.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25360.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 24957.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null + "xaxis": "x2", + "y": [ + 15603, + 15748, + 15831, + 15832, + 15801, + 15795, + 15801, + 15906, + 15839, + 15898, + 15877, + 15906, + 15868, + 15858, + 15839, + 15818, + 15901, + 15916, + 15846, + 15870, + 15867, + 15774, + 15837, + 15860, + 15820, + 15832, + 15811, + 15805, + 15828, + 15857, + 15793, + 15692, + 15678, + 15734, + 15843, + 15861, + 15825, + 15859, + 15857, + 15850, + 15866, + 15873, + 15883, + 15863, + 15865, + 15869, + 15839, + 15773, + 15832, + 15847, + 15814, + 15854, + 15854, + 15818, + 15809, + 15845, + 15848, + 15847, + 15834, + 15822, + 15826, + 15823, + 15843, + 15834, + 15854, + 15820, + 15830, + 15819, + 15815, + 15805, + 15842, + 15831, + 15879, + 15912, + 15848, + 15827, + 15837, + 15866, + 15842, + 15811, + 15837, + 15836, + 15815, + 15897, + 15820, + 15824, + 15802, + 15821, + 15807, + 15786, + 15823, + 15755, + 15675, + 15678, + 15700, + 15707, + 15673, + 15658, + 15592, + 15637, + 15631, + 15650, + 15619, + 15585, + 15647, + 15622, + 15550, + 15569, + 15544, + 15560, + 15606, + 15579, + 15578, + 15599, + 15578, + 15561, + 15532, + 15562, + 15565, + 15580, + 15585, + 15581, + 15529, + 15473, + 15501, + 15452, + 15438, + 15523, + 15415, + 15454, + 15497, + 15518, + 15479, + 15451, + 15452, + 15476, + 15445, + 15461, + 15510, + 15504, + 15489, + 15470, + 15444, + 15439, + 15437, + 15433, + 15425, + 15426, + 15439, + 15465, + 15462, + 15442, + 15466, + 15504, + 15516, + 15533, + 15483, + 15569, + 15549, + 15661, + 15653, + 15660, + 15629, + 15692, + 15666, + 15682, + 15682, + 15605, + 15639, + 15621, + 15637, + 15710, + 15859, + 15874, + 15833, + 15790, + 15763, + 15729, + 15770, + 15784, + 15803, + 15929, + 15921, + 15897, + 15869, + 15869, + 15856, + 15889, + 15936, + 15872, + 15849, + 15896, + 15838, + 15866, + 15863, + 15847, + 15797, + 15848, + 15895, + 16001, + 15970, + 16001, + 15992, + 16001, + 15975, + 15991, + 15988, + 15966, + 15953, + 15944, + 15887, + 15871, + 15823, + 15888, + 15891, + 15924, + 15926, + 15922, + 15910, + 15929, + 15888, + 15887, + 15901, + 15926, + 15919, + 15910, + 15904, + 15911, + 15912, + 15921, + 15923, + 15904, + 15911, + 15910, + 15883, + 15962, + 15883, + 15879, + 15918, + 16069, + 16114, + 16126, + 16091, + 16114, + 16156, + 16148, + 16070, + 16073, + 16150, + 16101, + 16016, + 16017, + 16027, + 16035, + 16043, + 16010, + 16050, + 16065, + 16074, + 16144, + 16135, + 16222, + 16287, + 16235, + 16282, + 16240, + 16171, + 16238, + 16245, + 16230, + 16210, + 16124, + 16171, + 16299, + 16307, + 16635, + 16923, + 16922, + 16795, + 16836, + 16875, + 16914, + 16896, + 16736, + 17360, + 17430, + 17350, + 17374, + 17297, + 17389, + 17342, + 17347, + 17462, + 17515, + 17482, + 17816, + 17800, + 17880, + 18281, + 18384, + 19320, + 19309, + 19211, + 19301, + 18935, + 19122, + 19328, + 19164, + 19137, + 19170, + 19260, + 19347, + 19157, + 19081, + 19129, + 19092, + 19136, + 19102, + 19068, + 19281, + 19301, + 19243, + 19301, + 19277, + 19496, + 19412, + 19533, + 19500, + 19244, + 19237, + 19251, + 19381, + 19524, + 19700, + 19510, + 19566, + 19466, + 19529, + 19541, + 19536, + 19595, + 19579, + 19606, + 19628, + 19694, + 19763, + 19751, + 19610, + 19675, + 19759, + 19783, + 19696, + 19548, + 19616, + 19722, + 19436, + 19338, + 19347, + 19225, + 19133, + 19171, + 19185, + 19278, + 19224, + 19205, + 19131, + 19233, + 19337, + 19265, + 19488, + 19338, + 19447, + 19499, + 19465, + 19367, + 19339, + 19365, + 19340, + 19487, + 19540, + 19678, + 19797, + 20531, + 20858, + 20779, + 20757, + 20790, + 20848, + 21235, + 21113, + 21118, + 21186, + 21426, + 21469, + 21376, + 20990, + 21030, + 20981, + 21108, + 21128, + 21088, + 20985, + 21121, + 20997, + 21131, + 20936, + 20794, + 20912, + 20868, + 20793, + 20876, + 20813, + 20884, + 21062, + 21005, + 21046, + 21230, + 21003, + 21145, + 21078, + 21242, + 21234, + 21262, + 21160, + 21053, + 21089, + 21054, + 21068, + 21032, + 21147, + 21054, + 20799, + 20665, + 20764, + 20817, + 20897, + 20777, + 20784, + 20771, + 20693, + 20707, + 20854, + 21615, + 21126, + 21217, + 21176, + 21226, + 21045, + 21070, + 21108, + 21216, + 21151, + 21136, + 21173, + 21196, + 21112, + 20772, + 20957, + 21162, + 21206, + 21083, + 21110, + 21078, + 21188, + 21365, + 21389, + 21248, + 21257, + 21241, + 21304, + 21243, + 21182, + 21160, + 21168, + 21169, + 21194, + 21176, + 21224, + 21172, + 21212, + 21380, + 21337, + 21400, + 21369, + 21421, + 21540, + 21665, + 21628, + 21693, + 21988, + 21913, + 21840, + 21766, + 21810, + 21835, + 21785, + 21251, + 21191, + 21176, + 21341, + 21296, + 21000, + 20977, + 21067, + 21050, + 21062, + 21000, + 21206, + 21138, + 21112, + 21308, + 21303, + 21318, + 21317, + 21132, + 21311, + 21282, + 21293, + 21270, + 21213, + 21141, + 21172, + 21180, + 21058, + 21092, + 21321, + 21550, + 21550, + 21752, + 21632, + 21611, + 21625, + 21646, + 21646, + 21714, + 21812, + 21909, + 21827, + 21507, + 21532, + 21638, + 21600, + 21578, + 21532, + 21445, + 21554, + 21553, + 21730, + 21731, + 21562, + 21671, + 21687, + 21659, + 21600, + 21601, + 21622, + 21634, + 21655, + 21758, + 21695, + 21729, + 21711, + 21671, + 21626, + 21616, + 21623, + 21656, + 21680, + 21661, + 21654, + 21491, + 21398, + 21169, + 21216, + 21248, + 21269, + 21301, + 21229, + 21099, + 21250, + 21227, + 21239, + 21209, + 21459, + 21499, + 21453, + 21369, + 21216, + 21248, + 21298, + 21341, + 21354, + 21481, + 21462, + 21495, + 21400, + 21616, + 21546, + 21604, + 21661, + 21673, + 21670, + 21630, + 21576, + 21552, + 21570, + 21525, + 21332, + 21435, + 21339, + 21425, + 21445, + 21378, + 20992, + 21073, + 21137, + 21130, + 21081, + 21059, + 21012, + 20974, + 20522, + 20348, + 20303, + 20406, + 20349, + 20329, + 20400, + 20400, + 20318, + 20371, + 20296, + 20346, + 20392, + 20180, + 20275, + 20275, + 20320, + 20318, + 20333, + 20334, + 20334, + 20392, + 20350, + 20322, + 20312, + 20468, + 20505, + 20446, + 20409, + 20437, + 20428, + 20551, + 20522, + 20475, + 20573, + 20623, + 20651, + 20385, + 20421, + 20342, + 20470, + 20471, + 20493, + 20240, + 20231, + 20200, + 20139, + 20040, + 20160, + 20148, + 20302, + 20223, + 20225, + 20251, + 20304, + 20281, + 20294, + 20199, + 20595, + 20547, + 20704, + 20709, + 20690, + 20597, + 20601, + 20668, + 20629, + 20679, + 20950, + 21220, + 21370, + 21370, + 21799, + 22596, + 22714, + 22969, + 23072, + 23014, + 22929, + 22992, + 22947, + 22852, + 23500, + 23268, + 23264, + 22982, + 22043, + 22395, + 22378, + 22286, + 22224, + 22334, + 22393, + 22360, + 22653, + 22754, + 22936, + 22861, + 22970, + 23068, + 22987, + 23049, + 22925, + 22971, + 22913, + 22971, + 23065, + 23082, + 23022, + 23018, + 23029, + 23108, + 23049, + 23127, + 22982, + 22981, + 23083, + 23089, + 23265, + 22780, + 22951, + 22961, + 22724, + 22715, + 22867, + 22921, + 22905, + 23265, + 23277, + 23242, + 23315, + 23221, + 23243, + 23186, + 23250, + 23299, + 23369, + 23363, + 23436, + 23261, + 23066, + 23075, + 23000, + 23156, + 23126, + 22748, + 22974, + 22715, + 22721, + 22634, + 22582, + 22674, + 22746, + 22669, + 22338, + 22352, + 22463, + 22440, + 22816, + 22787, + 23028, + 22950, + 22936, + 22929, + 22467, + 22644, + 22590, + 22537, + 22650, + 22556, + 22602, + 22652, + 22628, + 22573, + 22530, + 22543, + 22586, + 22526, + 22176, + 21906, + 21965, + 21936, + 22009, + 22016, + 21904, + 21881, + 21943, + 21899, + 21801, + 21812, + 21857, + 21875, + 21808, + 21822, + 22067, + 21989, + 22103, + 22000, + 22046, + 22178, + 22130, + 22082, + 22146, + 22085, + 22362, + 22389, + 22335, + 22333, + 22334, + 22194, + 22209, + 22143, + 22198, + 22416, + 22247, + 22034, + 21967, + 22035, + 22142, + 22153, + 22167, + 22104, + 21937, + 21953, + 22068, + 22030, + 22180, + 22181, + 21991, + 21895, + 21885, + 21986, + 22143, + 22357, + 22269, + 22330, + 22249, + 22205, + 22210, + 22237, + 21929, + 22089, + 22163, + 22089, + 22090, + 22036, + 21980, + 22042, + 22030, + 22028, + 22010, + 21992, + 22135, + 22102, + 22115, + 20961, + 21075, + 21086, + 21121, + 21104, + 21076, + 21116, + 21098, + 21104, + 21035, + 20917, + 21031, + 21026, + 21039, + 21017, + 21034, + 21007, + 21043, + 21047, + 20998, + 20979, + 20931, + 20864, + 21008, + 21256, + 21051, + 21092, + 21071, + 21052, + 21066, + 21148, + 21124, + 21094, + 21093, + 21171, + 21126, + 21073, + 21046, + 21009, + 21030, + 21079, + 21010, + 21056, + 21139, + 21049, + 20971, + 20972, + 20962, + 21051, + 21000, + 21022, + 20977, + 20996, + 20965, + 20996, + 21065, + 21112, + 20968, + 20913, + 21042, + 21019, + 21024, + 20849, + 20812, + 20887, + 20948, + 20859, + 20983, + 20895, + 20853, + 20845, + 20587, + 20593, + 20641, + 20577, + 20520, + 20475, + 20482, + 20518, + 20446, + 20269, + 19683, + 19145, + 19257, + 18984, + 18946, + 18903, + 18843, + 18779, + 18673, + 19026, + 18760, + 18697, + 18844, + 18902, + 18989, + 19567, + 19342, + 19277, + 18786, + 18949, + 19034, + 18942, + 18888, + 19017, + 19050, + 19155, + 19219, + 19217, + 19181, + 19200, + 19195, + 19216, + 19237, + 19209, + 19257, + 19638, + 19577, + 19974, + 20665, + 20993, + 20775, + 20880, + 20891, + 20562, + 20616, + 21000, + 22323, + 22407, + 22505, + 22192, + 22338, + 22600, + 22678, + 22547, + 22488, + 22613, + 23072, + 23972, + 24160, + 23810, + 23275, + 22855, + 23000, + 23137, + 22987, + 23006, + 23194, + 22994, + 23422, + 23514, + 23244, + 23036, + 23052, + 23163, + 22973, + 22982, + 22945, + 23000, + 23212, + 23257, + 23469, + 23433, + 23475, + 23324, + 23590, + 23523, + 23605, + 24196, + 24237, + 24200, + 24511, + 24721, + 25360, + 24911, + 24863, + 24884, + 25192, + 25426, + 25656, + 25591, + 25620, + 25879, + 25699, + 25708, + 25765, + 25732, + 25518, + 25591, + 25678, + 25568, + 25245, + 25419, + 25539, + 25411, + 25280, + 25350, + 25492, + 25506, + 25854, + 26222, + 26549, + 26259, + 26264, + 25987, + 25661, + 25882, + 26519, + 26484, + 26441, + 26025, + 25908, + 25882, + 25967, + 26166, + 25952, + 26091, + 25968, + 26048, + 25756, + 26098, + 26103, + 26100, + 26100, + 26055, + 26200, + 26083, + 26173, + 26158, + 26200, + 26279, + 26160, + 26119, + 26132, + 26340, + 26535, + 26451, + 24551, + 25010, + 25158, + 25005, + 25207, + 25443, + 25420, + 25450, + 25447, + 25207, + 26296, + 26075, + 26216, + 26042, + 26145, + 26121, + 26102, + 26157, + 26130, + 26149, + 25769, + 25967, + 26064, + 25672, + 25921, + 25508, + 25596, + 25615, + 25686, + 25588, + 25622, + 25596, + 25581, + 25679, + 25719, + 25604, + 25323, + 25562, + 25570, + 25708, + 25618, + 25631, + 25619, + 25740, + 25940, + 26165, + 25902, + 25922, + 25908, + 25911, + 25968, + 25864, + 25901, + 25725, + 25873, + 25956, + 25886, + 25697, + 24973, + 25000, + 25003, + 25180, + 25140, + 25062, + 24966, + 24972, + 24920, + 24880, + 25012, + 24832, + 24911, + 24798, + 25268, + 25063, + 25187, + 25230, + 25256, + 25449, + 25892, + 26044, + 26073, + 26175, + 26240, + 26018, + 26198, + 26089, + 26155, + 26185, + 26256, + 26394, + 26493, + 26341, + 26343, + 26122, + 25999, + 25579, + 25686, + 25775, + 25760, + 25833, + 25871, + 25757, + 25510, + 25596, + 25714, + 26017, + 26194, + 26113, + 26237, + 26269, + 26233, + 26388, + 26302, + 26327, + 26207, + 26231, + 26200, + 26205, + 26212, + 26192, + 26161, + 26296, + 26234, + 26211, + 26245, + 26259, + 26195, + 26216, + 26156, + 26103, + 25979, + 26061, + 25837, + 25868, + 26082, + 25668, + 25756, + 25701, + 25796, + 26128, + 25984, + 25986, + 25810, + 25745, + 25793, + 25448, + 25507, + 25520, + 25656, + 25644, + 25662, + 25828, + 25942, + 25753, + 25622, + 25790, + 25746, + 25782, + 25711, + 26066, + 26029, + 26100, + 26041, + 26044, + 26108, + 25987, + 25718, + 25755, + 25926, + 25856, + 25857, + 25780, + 25855, + 25766, + 25615, + 25593, + 25630, + 25624, + 25717, + 25674, + 25714, + 25678, + 25715, + 25735, + 25722, + 25672, + 25535, + 25571, + 25534, + 25673, + 25662, + 25632, + 25622, + 25600, + 25629, + 25638, + 25731, + 25818, + 25812, + 25741, + 25754, + 25742, + 25722, + 25689, + 25634, + 25658, + 25685, + 25821, + 25787, + 25750, + 25650, + 25651, + 25677, + 25665, + 25650, + 25644, + 25811, + 26090, + 25952, + 25970, + 25961, + 25989, + 25967, + 25980, + 26034, + 26011, + 26236, + 26837, + 26853, + 26889, + 27264, + 27668, + 27647, + 27387, + 27615, + 27545, + 27537, + 27687, + 27700, + 27678, + 27620, + 27692, + 27652, + 27649, + 27414, + 27382, + 27452, + 27490, + 27503, + 27405, + 27317, + 27316, + 27102, + 27258, + 27179, + 27357, + 27379, + 27419, + 27368, + 27413, + 27410, + 27334, + 27567, + 27580, + 27513, + 27451, + 27541, + 27842, + 27730, + 27847, + 27799, + 27854, + 27905, + 27890, + 27554, + 27586, + 27617, + 27730, + 27706, + 27639, + 27639, + 27696, + 27717, + 27748, + 27665, + 27640, + 27610, + 27570, + 27621, + 27576, + 27577, + 27544, + 27611, + 27652, + 27583, + 27548, + 27541, + 27616, + 27585, + 27594, + 27659, + 27625, + 27336, + 27340, + 27321, + 27134, + 27243, + 26973, + 26894, + 26923, + 27003, + 27039, + 27020, + 26978, + 26955, + 26998, + 27100, + 27212, + 27258, + 27680, + 27715, + 27550, + 27389, + 27583, + 27689, + 27691, + 27575, + 27552, + 27635, + 27457, + 26751, + 26844, + 26801, + 26747, + 26790, + 26703, + 26641, + 26343, + 26409, + 26364, + 26383, + 26358, + 26342, + 26113, + 26275, + 25972, + 25955, + 25644, + 25750, + 25757, + 25734, + 25831, + 25681, + 25693, + 25630, + 25552, + 25657, + 25557, + 25403, + 24834, + 24833, + 24793, + 24831, + 24842, + 24951, + 24860, + 24829, + 24880, + 24895, + 25104, + 25341, + 25250, + 25330, + 25503, + 25182, + 25148, + 25238, + 25399, + 25320, + 25280, + 25267, + 25084, + 25131, + 25092, + 25063, + 25136, + 25267, + 25326, + 25247, + 24979, + 24839, + 25034, + 25067, + 24732, + 24800, + 24810, + 24824, + 24876, + 24792, + 24727, + 24794, + 24712, + 24763, + 24869, + 24859, + 24937, + 25203, + 25197, + 25782, + 25795, + 25844, + 25800, + 25868, + 25763, + 26157, + 26188, + 26910, + 26986, + 26920, + 25365, + 25999, + 25735, + 26034, + 26162, + 26324, + 26180, + 26239, + 26238, + 26273, + 26463, + 26576, + 26888, + 26879, + 26735, + 26777, + 26686, + 26781, + 26792, + 26587, + 26678, + 26404, + 26426, + 26547, + 26617, + 26620, + 26588, + 26576, + 26646, + 26659, + 26657, + 26586, + 26619, + 26701, + 26595, + 26486, + 26561, + 26546, + 26529, + 26425, + 26481, + 26568, + 26579, + 26556, + 26514, + 26580, + 26920, + 26887, + 26667, + 26703, + 26526, + 25946, + 25963, + 25913, + 26039, + 25944, + 25948, + 25865, + 25717, + 25801, + 25397, + 25548, + 25603, + 25503, + 25586, + 25521, + 25538, + 25559, + 25630, + 25583, + 25980, + 26003, + 26062, + 26053, + 26032, + 25883, + 25869, + 25860, + 25981, + 25971, + 25874, + 25732, + 25645, + 25847, + 25656, + 26152, + 26182, + 26226, + 26246, + 26373, + 26262, + 26316, + 26338, + 26102, + 26257, + 26161, + 26241, + 26207, + 26161, + 26548, + 26434, + 26453, + 26344, + 26396, + 26462, + 26565, + 26642, + 26763, + 26817, + 26762, + 26783, + 26837, + 26658, + 26631, + 26673, + 26626, + 26609, + 26316, + 26007, + 26198, + 26253, + 26220, + 26256, + 26236, + 26211, + 26233, + 26308, + 26205, + 26229, + 26275, + 26322, + 26291, + 26322, + 26137, + 25868, + 25639, + 25606, + 25659, + 25382, + 25359, + 25358, + 25264, + 25340, + 25106, + 24889, + 25039, + 25197, + 25263, + 25174, + 25067, + 25221, + 25121, + 25272, + 25223, + 25045, + 25213, + 25280, + 25189, + 25217, + 25265, + 25238, + 25255, + 25137, + 25159, + 25275, + 25666, + 25683, + 25093, + 25231, + 25082, + 25156, + 25094, + 25024, + 25106, + 25130, + 25106, + 25080, + 24931, + 24904, + 24687, + 24600, + 24729, + 24748, + 24606, + 24327, + 24084, + 24127, + 24202, + 24214, + 24309, + 24254, + 24234, + 24397, + 24691, + 24695, + 24743, + 24725, + 24662, + 24689, + 24759, + 24731, + 24741, + 24746, + 24745, + 24782, + 24797, + 24708, + 24614, + 24784, + 24746, + 24748, + 24767, + 24714, + 24817, + 24959, + 24847, + 24777, + 24790, + 24825, + 25053, + 25082, + 25170, + 25208, + 25223, + 25152, + 25175, + 25200, + 25284, + 25209, + 25173, + 24984, + 24835, + 24945, + 24893, + 25048, + 24865, + 24854, + 24810, + 24886, + 24923, + 24802, + 24857, + 24888, + 24991, + 24912, + 24851, + 24781, + 24817, + 24600, + 24623, + 24753, + 25009, + 25263, + 25228, + 25260, + 25182, + 25225, + 25119, + 25317, + 25332, + 25322, + 25285, + 25115, + 24611, + 24827, + 24977, + 24893, + 24909, + 24925, + 24942, + 24921, + 24855, + 24865, + 24903, + 24864, + 24875, + 24849, + 24910, + 24878, + 24870, + 24869, + 24876, + 24864, + 24899, + 24893, + 24889, + 24954, + 25120, + 25044, + 25024, + 25089, + 25114, + 25151, + 25077, + 25022, + 25028, + 24836, + 24873, + 24889, + 24858, + 24901, + 24763, + 24711, + 24589, + 24635, + 24762, + 24845, + 24821, + 24798, + 24977, + 24880, + 24831, + 24820, + 24884, + 24842, + 24970, + 25304, + 25360, + 25258, + 25282, + 25359, + 25253, + 25352, + 25229, + 25251, + 25225, + 25274, + 25203, + 24840, + 24837, + 24742, + 24829, + 24819, + 24579, + 24458, + 24438, + 24407, + 24537, + 24500, + 24311, + 24435, + 24525, + 24399, + 24500, + 24518, + 24589, + 24441, + 24566, + 24675, + 24689, + 24695, + 24615, + 24631, + 24545, + 24674, + 24652, + 24619, + 24734, + 24965, + 24909, + 24954, + 24911, + 24910, + 24894, + 24957, + 24909, + 24926, + 24910, + 24908, + 24844, + 24893, + 24945, + 24933, + 24953, + 25047, + 25238, + 25322, + 25309, + 25346, + 25292, + 25300, + 25301, + 25402, + 25470, + 25627, + 25948, + 26162, + 26245, + 26038, + 26053, + 26006, + 26053, + 26056, + 26035, + 25780, + 25856, + 25800, + 25837, + 25915, + 25893, + 25973, + 25960, + 25951, + 26001, + 26092, + 25921, + 25811, + 25866, + 25956, + 25835, + 25820, + 25904, + 25850, + 25332, + 25452, + 25431, + 25389, + 25380, + 25278, + 25388, + 25292, + 25350, + 25469, + 25358 + ], + "yaxis": "y2" + }, + { + "line": { + "color": "blue", + "width": 1 + }, + "mode": "lines", + "name": "Close side ways", + "type": "scatter", + "x": [ + "2022-06-10T02:00:00", + "2022-06-10T04:00:00", + "2022-06-10T06:00:00", + "2022-06-10T08:00:00", + "2022-06-10T10:00:00", + "2022-06-10T12:00:00", + "2022-06-10T14:00:00", + "2022-06-10T16:00:00", + "2022-06-10T18:00:00", + "2022-06-10T20:00:00", + "2022-06-10T22:00:00", + "2022-06-11T00:00:00", + "2022-06-11T02:00:00", + "2022-06-11T04:00:00", + "2022-06-11T06:00:00", + "2022-06-11T08:00:00", + "2022-06-11T10:00:00", + "2022-06-11T12:00:00", + "2022-06-11T14:00:00", + "2022-06-11T16:00:00", + "2022-06-11T18:00:00", + "2022-06-11T20:00:00", + "2022-06-11T22:00:00", + "2022-06-12T00:00:00", + "2022-06-12T02:00:00", + "2022-06-12T04:00:00", + "2022-06-12T06:00:00", + "2022-06-12T08:00:00", + "2022-06-12T10:00:00", + "2022-06-12T12:00:00", + "2022-06-12T14:00:00", + "2022-06-12T16:00:00", + "2022-06-12T18:00:00", + "2022-06-12T20:00:00", + "2022-06-12T22:00:00", + "2022-06-13T00:00:00", + "2022-06-13T02:00:00", + "2022-06-13T04:00:00", + "2022-06-13T06:00:00", + "2022-06-13T08:00:00", + "2022-06-13T10:00:00", + "2022-06-13T12:00:00", + "2022-06-13T14:00:00", + "2022-06-13T16:00:00", + "2022-06-13T18:00:00", + "2022-06-13T20:00:00", + "2022-06-13T22:00:00", + "2022-06-14T00:00:00", + "2022-06-14T02:00:00", + "2022-06-14T04:00:00", + "2022-06-14T06:00:00", + "2022-06-14T08:00:00", + "2022-06-14T10:00:00", + "2022-06-14T12:00:00", + "2022-06-14T14:00:00", + "2022-06-14T16:00:00", + "2022-06-14T18:00:00", + "2022-06-14T20:00:00", + "2022-06-14T22:00:00", + "2022-06-15T00:00:00", + "2022-06-15T02:00:00", + "2022-06-15T04:00:00", + "2022-06-15T06:00:00", + "2022-06-15T08:00:00", + "2022-06-15T10:00:00", + "2022-06-15T12:00:00", + "2022-06-15T14:00:00", + "2022-06-15T16:00:00", + "2022-06-15T18:00:00", + "2022-06-15T20:00:00", + "2022-06-15T22:00:00", + "2022-06-16T00:00:00", + "2022-06-16T02:00:00", + "2022-06-16T04:00:00", + "2022-06-16T06:00:00", + "2022-06-16T08:00:00", + "2022-06-16T10:00:00", + "2022-06-16T12:00:00", + "2022-06-16T14:00:00", + "2022-06-16T16:00:00", + "2022-06-16T18:00:00", + "2022-06-16T20:00:00", + "2022-06-16T22:00:00", + "2022-06-17T00:00:00", + "2022-06-17T02:00:00", + "2022-06-17T04:00:00", + "2022-06-17T06:00:00", + "2022-06-17T08:00:00", + "2022-06-17T10:00:00", + "2022-06-17T12:00:00", + "2022-06-17T14:00:00", + "2022-06-17T16:00:00", + "2022-06-17T18:00:00", + "2022-06-17T20:00:00", + "2022-06-17T22:00:00", + "2022-06-18T00:00:00", + "2022-06-18T02:00:00", + "2022-06-18T04:00:00", + "2022-06-18T06:00:00", + "2022-06-18T08:00:00", + "2022-06-18T10:00:00", + "2022-06-18T12:00:00", + "2022-06-18T14:00:00", + "2022-06-18T16:00:00", + "2022-06-18T18:00:00", + "2022-06-18T20:00:00", + "2022-06-18T22:00:00", + "2022-06-19T00:00:00", + "2022-06-19T02:00:00", + "2022-06-19T04:00:00", + "2022-06-19T06:00:00", + "2022-06-19T08:00:00", + "2022-06-19T10:00:00", + "2022-06-19T12:00:00", + "2022-06-19T14:00:00", + "2022-06-19T16:00:00", + "2022-06-19T18:00:00", + "2022-06-19T20:00:00", + "2022-06-19T22:00:00", + "2022-06-20T00:00:00", + "2022-06-20T02:00:00", + "2022-06-20T04:00:00", + "2022-06-20T06:00:00", + "2022-06-20T08:00:00", + "2022-06-20T10:00:00", + "2022-06-20T12:00:00", + "2022-06-20T14:00:00", + "2022-06-20T16:00:00", + "2022-06-20T18:00:00", + "2022-06-20T20:00:00", + "2022-06-20T22:00:00", + "2022-06-21T00:00:00", + "2022-06-21T02:00:00", + "2022-06-21T04:00:00", + "2022-06-21T06:00:00", + "2022-06-21T08:00:00", + "2022-06-21T10:00:00", + "2022-06-21T12:00:00", + "2022-06-21T14:00:00", + "2022-06-21T16:00:00", + "2022-06-21T18:00:00", + "2022-06-21T20:00:00", + "2022-06-21T22:00:00", + "2022-06-22T00:00:00", + "2022-06-22T02:00:00", + "2022-06-22T04:00:00", + "2022-06-22T06:00:00", + "2022-06-22T08:00:00", + "2022-06-22T10:00:00", + "2022-06-22T12:00:00", + "2022-06-22T14:00:00", + "2022-06-22T16:00:00", + "2022-06-22T18:00:00", + "2022-06-22T20:00:00", + "2022-06-22T22:00:00", + "2022-06-23T00:00:00", + "2022-06-23T02:00:00", + "2022-06-23T04:00:00", + "2022-06-23T06:00:00", + "2022-06-23T08:00:00", + "2022-06-23T10:00:00", + "2022-06-23T12:00:00", + "2022-06-23T14:00:00", + "2022-06-23T16:00:00", + "2022-06-23T18:00:00", + "2022-06-23T20:00:00", + "2022-06-23T22:00:00", + "2022-06-24T00:00:00", + "2022-06-24T02:00:00", + "2022-06-24T04:00:00", + "2022-06-24T06:00:00", + "2022-06-24T08:00:00", + "2022-06-24T10:00:00", + "2022-06-24T12:00:00", + "2022-06-24T14:00:00", + "2022-06-24T16:00:00", + "2022-06-24T18:00:00", + "2022-06-24T20:00:00", + "2022-06-24T22:00:00", + "2022-06-25T00:00:00", + "2022-06-25T02:00:00", + "2022-06-25T04:00:00", + "2022-06-25T06:00:00", + "2022-06-25T08:00:00", + "2022-06-25T10:00:00", + "2022-06-25T12:00:00", + "2022-06-25T14:00:00", + "2022-06-25T16:00:00", + "2022-06-25T18:00:00", + "2022-06-25T20:00:00", + "2022-06-25T22:00:00", + "2022-06-26T00:00:00", + "2022-06-26T02:00:00", + "2022-06-26T04:00:00", + "2022-06-26T06:00:00", + "2022-06-26T08:00:00", + "2022-06-26T10:00:00", + "2022-06-26T12:00:00", + "2022-06-26T14:00:00", + "2022-06-26T16:00:00", + "2022-06-26T18:00:00", + "2022-06-26T20:00:00", + "2022-06-26T22:00:00", + "2022-06-27T00:00:00", + "2022-06-27T02:00:00", + "2022-06-27T04:00:00", + "2022-06-27T06:00:00", + "2022-06-27T08:00:00", + "2022-06-27T10:00:00", + "2022-06-27T12:00:00", + "2022-06-27T14:00:00", + "2022-06-27T16:00:00", + "2022-06-27T18:00:00", + "2022-06-27T20:00:00", + "2022-06-27T22:00:00", + "2022-06-28T00:00:00", + "2022-06-28T02:00:00", + "2022-06-28T04:00:00", + "2022-06-28T06:00:00", + "2022-06-28T08:00:00", + "2022-06-28T10:00:00", + "2022-06-28T12:00:00", + "2022-06-28T14:00:00", + "2022-06-28T16:00:00", + "2022-06-28T18:00:00", + "2022-06-28T20:00:00", + "2022-06-28T22:00:00", + "2022-06-29T00:00:00", + "2022-06-29T02:00:00", + "2022-06-29T04:00:00", + "2022-06-29T06:00:00", + "2022-06-29T08:00:00", + "2022-06-29T10:00:00", + "2022-06-29T12:00:00", + "2022-06-29T14:00:00", + "2022-06-29T16:00:00", + "2022-06-29T18:00:00", + "2022-06-29T20:00:00", + "2022-06-29T22:00:00", + "2022-06-30T00:00:00", + "2022-06-30T02:00:00", + "2022-06-30T04:00:00", + "2022-06-30T06:00:00", + "2022-06-30T08:00:00", + "2022-06-30T10:00:00", + "2022-06-30T12:00:00", + "2022-06-30T14:00:00", + "2022-06-30T16:00:00", + "2022-06-30T18:00:00", + "2022-06-30T20:00:00", + "2022-06-30T22:00:00", + "2022-07-01T00:00:00", + "2022-07-01T02:00:00", + "2022-07-01T04:00:00", + "2022-07-01T06:00:00", + "2022-07-01T08:00:00", + "2022-07-01T10:00:00", + "2022-07-01T12:00:00", + "2022-07-01T14:00:00", + "2022-07-01T16:00:00", + "2022-07-01T18:00:00", + "2022-07-01T20:00:00", + "2022-07-01T22:00:00", + "2022-07-02T00:00:00", + "2022-07-02T02:00:00", + "2022-07-02T04:00:00", + "2022-07-02T06:00:00", + "2022-07-02T08:00:00", + "2022-07-02T10:00:00", + "2022-07-02T12:00:00", + "2022-07-02T14:00:00", + "2022-07-02T16:00:00", + "2022-07-02T18:00:00", + "2022-07-02T20:00:00", + "2022-07-02T22:00:00", + "2022-07-03T00:00:00", + "2022-07-03T02:00:00", + "2022-07-03T04:00:00", + "2022-07-03T06:00:00", + "2022-07-03T08:00:00", + "2022-07-03T10:00:00", + "2022-07-03T12:00:00", + "2022-07-03T14:00:00", + "2022-07-03T16:00:00", + "2022-07-03T18:00:00", + "2022-07-03T20:00:00", + "2022-07-03T22:00:00", + "2022-07-04T00:00:00", + "2022-07-04T02:00:00", + "2022-07-04T04:00:00", + "2022-07-04T06:00:00", + "2022-07-04T08:00:00", + "2022-07-04T10:00:00", + "2022-07-04T12:00:00", + "2022-07-04T14:00:00", + "2022-07-04T16:00:00", + "2022-07-04T18:00:00", + "2022-07-04T20:00:00", + "2022-07-04T22:00:00", + "2022-07-05T00:00:00", + "2022-07-05T02:00:00", + "2022-07-05T04:00:00", + "2022-07-05T06:00:00", + "2022-07-05T08:00:00", + "2022-07-05T10:00:00", + "2022-07-05T12:00:00", + "2022-07-05T14:00:00", + "2022-07-05T16:00:00", + "2022-07-05T18:00:00", + "2022-07-05T20:00:00", + "2022-07-05T22:00:00", + "2022-07-06T00:00:00", + "2022-07-06T02:00:00", + "2022-07-06T04:00:00", + "2022-07-06T06:00:00", + "2022-07-06T08:00:00", + "2022-07-06T10:00:00", + "2022-07-06T12:00:00", + "2022-07-06T14:00:00", + "2022-07-06T16:00:00", + "2022-07-06T18:00:00", + "2022-07-06T20:00:00", + "2022-07-06T22:00:00", + "2022-07-07T00:00:00", + "2022-07-07T02:00:00", + "2022-07-07T04:00:00", + "2022-07-07T06:00:00", + "2022-07-07T08:00:00", + "2022-07-07T10:00:00", + "2022-07-07T12:00:00", + "2022-07-07T14:00:00", + "2022-07-07T16:00:00", + "2022-07-07T18:00:00", + "2022-07-07T20:00:00", + "2022-07-07T22:00:00", + "2022-07-08T00:00:00", + "2022-07-08T02:00:00", + "2022-07-08T04:00:00", + "2022-07-08T06:00:00", + "2022-07-08T08:00:00", + "2022-07-08T10:00:00", + "2022-07-08T12:00:00", + "2022-07-08T14:00:00", + "2022-07-08T16:00:00", + "2022-07-08T18:00:00", + "2022-07-08T20:00:00", + "2022-07-08T22:00:00", + "2022-07-09T00:00:00", + "2022-07-09T02:00:00", + "2022-07-09T04:00:00", + "2022-07-09T06:00:00", + "2022-07-09T08:00:00", + "2022-07-09T10:00:00", + "2022-07-09T12:00:00", + "2022-07-09T14:00:00", + "2022-07-09T16:00:00", + "2022-07-09T18:00:00", + "2022-07-09T20:00:00", + "2022-07-09T22:00:00", + "2022-07-10T00:00:00", + "2022-07-10T02:00:00", + "2022-07-10T04:00:00", + "2022-07-10T06:00:00", + "2022-07-10T08:00:00", + "2022-07-10T10:00:00", + "2022-07-10T12:00:00", + "2022-07-10T14:00:00", + "2022-07-10T16:00:00", + "2022-07-10T18:00:00", + "2022-07-10T20:00:00", + "2022-07-10T22:00:00", + "2022-07-11T00:00:00", + "2022-07-11T02:00:00", + "2022-07-11T04:00:00", + "2022-07-11T06:00:00", + "2022-07-11T08:00:00", + "2022-07-11T10:00:00", + "2022-07-11T12:00:00", + "2022-07-11T14:00:00", + "2022-07-11T16:00:00", + "2022-07-11T18:00:00", + "2022-07-11T20:00:00", + "2022-07-11T22:00:00", + "2022-07-12T00:00:00", + "2022-07-12T02:00:00", + "2022-07-12T04:00:00", + "2022-07-12T06:00:00", + "2022-07-12T08:00:00", + "2022-07-12T10:00:00", + "2022-07-12T12:00:00", + "2022-07-12T14:00:00", + "2022-07-12T16:00:00", + "2022-07-12T18:00:00", + "2022-07-12T20:00:00", + "2022-07-12T22:00:00", + "2022-07-13T00:00:00", + "2022-07-13T02:00:00", + "2022-07-13T04:00:00", + "2022-07-13T06:00:00", + "2022-07-13T08:00:00", + "2022-07-13T10:00:00", + "2022-07-13T12:00:00", + "2022-07-13T14:00:00", + "2022-07-13T16:00:00", + "2022-07-13T18:00:00", + "2022-07-13T20:00:00", + "2022-07-13T22:00:00", + "2022-07-14T00:00:00", + "2022-07-14T02:00:00", + "2022-07-14T04:00:00", + "2022-07-14T06:00:00", + "2022-07-14T08:00:00", + "2022-07-14T10:00:00", + "2022-07-14T12:00:00", + "2022-07-14T14:00:00", + "2022-07-14T16:00:00", + "2022-07-14T18:00:00", + "2022-07-14T20:00:00", + "2022-07-14T22:00:00", + "2022-07-15T00:00:00", + "2022-07-15T02:00:00", + "2022-07-15T04:00:00", + "2022-07-15T06:00:00", + "2022-07-15T08:00:00", + "2022-07-15T10:00:00", + "2022-07-15T12:00:00", + "2022-07-15T14:00:00", + "2022-07-15T16:00:00", + "2022-07-15T18:00:00", + "2022-07-15T20:00:00", + "2022-07-15T22:00:00", + "2022-07-16T00:00:00", + "2022-07-16T02:00:00", + "2022-07-16T04:00:00", + "2022-07-16T06:00:00", + "2022-07-16T08:00:00", + "2022-07-16T10:00:00", + "2022-07-16T12:00:00", + "2022-07-16T14:00:00", + "2022-07-16T16:00:00", + "2022-07-16T18:00:00", + "2022-07-16T20:00:00", + "2022-07-16T22:00:00", + "2022-07-17T00:00:00", + "2022-07-17T02:00:00", + "2022-07-17T04:00:00", + "2022-07-17T06:00:00", + "2022-07-17T08:00:00", + "2022-07-17T10:00:00", + "2022-07-17T12:00:00", + "2022-07-17T14:00:00", + "2022-07-17T16:00:00", + "2022-07-17T18:00:00", + "2022-07-17T20:00:00", + "2022-07-17T22:00:00", + "2022-07-18T00:00:00", + "2022-07-18T02:00:00", + "2022-07-18T04:00:00", + "2022-07-18T06:00:00", + "2022-07-18T08:00:00", + "2022-07-18T10:00:00", + "2022-07-18T12:00:00", + "2022-07-18T14:00:00", + "2022-07-18T16:00:00", + "2022-07-18T18:00:00", + "2022-07-18T20:00:00", + "2022-07-18T22:00:00", + "2022-07-19T00:00:00", + "2022-07-19T02:00:00", + "2022-07-19T04:00:00", + "2022-07-19T06:00:00", + "2022-07-19T08:00:00", + "2022-07-19T10:00:00", + "2022-07-19T12:00:00", + "2022-07-19T14:00:00", + "2022-07-19T16:00:00", + "2022-07-19T18:00:00", + "2022-07-19T20:00:00", + "2022-07-19T22:00:00", + "2022-07-20T00:00:00", + "2022-07-20T02:00:00", + "2022-07-20T04:00:00", + "2022-07-20T06:00:00", + "2022-07-20T08:00:00", + "2022-07-20T10:00:00", + "2022-07-20T12:00:00", + "2022-07-20T14:00:00", + "2022-07-20T16:00:00", + "2022-07-20T18:00:00", + "2022-07-20T20:00:00", + "2022-07-20T22:00:00", + "2022-07-21T00:00:00", + "2022-07-21T02:00:00", + "2022-07-21T04:00:00", + "2022-07-21T06:00:00", + "2022-07-21T08:00:00", + "2022-07-21T10:00:00", + "2022-07-21T12:00:00", + "2022-07-21T14:00:00", + "2022-07-21T16:00:00", + "2022-07-21T18:00:00", + "2022-07-21T20:00:00", + "2022-07-21T22:00:00", + "2022-07-22T00:00:00", + "2022-07-22T02:00:00", + "2022-07-22T04:00:00", + "2022-07-22T06:00:00", + "2022-07-22T08:00:00", + "2022-07-22T10:00:00", + "2022-07-22T12:00:00", + "2022-07-22T14:00:00", + "2022-07-22T16:00:00", + "2022-07-22T18:00:00", + "2022-07-22T20:00:00", + "2022-07-22T22:00:00", + "2022-07-23T00:00:00", + "2022-07-23T02:00:00", + "2022-07-23T04:00:00", + "2022-07-23T06:00:00", + "2022-07-23T08:00:00", + "2022-07-23T10:00:00", + "2022-07-23T12:00:00", + "2022-07-23T14:00:00", + "2022-07-23T16:00:00", + "2022-07-23T18:00:00", + "2022-07-23T20:00:00", + "2022-07-23T22:00:00", + "2022-07-24T00:00:00", + "2022-07-24T02:00:00", + "2022-07-24T04:00:00", + "2022-07-24T06:00:00", + "2022-07-24T08:00:00", + "2022-07-24T10:00:00", + "2022-07-24T12:00:00", + "2022-07-24T14:00:00", + "2022-07-24T16:00:00", + "2022-07-24T18:00:00", + "2022-07-24T20:00:00", + "2022-07-24T22:00:00", + "2022-07-25T00:00:00", + "2022-07-25T02:00:00", + "2022-07-25T04:00:00", + "2022-07-25T06:00:00", + "2022-07-25T08:00:00", + "2022-07-25T10:00:00", + "2022-07-25T12:00:00", + "2022-07-25T14:00:00", + "2022-07-25T16:00:00", + "2022-07-25T18:00:00", + "2022-07-25T20:00:00", + "2022-07-25T22:00:00", + "2022-07-26T00:00:00", + "2022-07-26T02:00:00", + "2022-07-26T04:00:00", + "2022-07-26T06:00:00", + "2022-07-26T08:00:00", + "2022-07-26T10:00:00", + "2022-07-26T12:00:00", + "2022-07-26T14:00:00", + "2022-07-26T16:00:00", + "2022-07-26T18:00:00", + "2022-07-26T20:00:00", + "2022-07-26T22:00:00", + "2022-07-27T00:00:00", + "2022-07-27T02:00:00", + "2022-07-27T04:00:00", + "2022-07-27T06:00:00", + "2022-07-27T08:00:00", + "2022-07-27T10:00:00", + "2022-07-27T12:00:00", + "2022-07-27T14:00:00", + "2022-07-27T16:00:00", + "2022-07-27T18:00:00", + "2022-07-27T20:00:00", + "2022-07-27T22:00:00", + "2022-07-28T00:00:00", + "2022-07-28T02:00:00", + "2022-07-28T04:00:00", + "2022-07-28T06:00:00", + "2022-07-28T08:00:00", + "2022-07-28T10:00:00", + "2022-07-28T12:00:00", + "2022-07-28T14:00:00", + "2022-07-28T16:00:00", + "2022-07-28T18:00:00", + "2022-07-28T20:00:00", + "2022-07-28T22:00:00", + "2022-07-29T00:00:00", + "2022-07-29T02:00:00", + "2022-07-29T04:00:00", + "2022-07-29T06:00:00", + "2022-07-29T08:00:00", + "2022-07-29T10:00:00", + "2022-07-29T12:00:00", + "2022-07-29T14:00:00", + "2022-07-29T16:00:00", + "2022-07-29T18:00:00", + "2022-07-29T20:00:00", + "2022-07-29T22:00:00", + "2022-07-30T00:00:00", + "2022-07-30T02:00:00", + "2022-07-30T04:00:00", + "2022-07-30T06:00:00", + "2022-07-30T08:00:00", + "2022-07-30T10:00:00", + "2022-07-30T12:00:00", + "2022-07-30T14:00:00", + "2022-07-30T16:00:00", + "2022-07-30T18:00:00", + "2022-07-30T20:00:00", + "2022-07-30T22:00:00", + "2022-07-31T00:00:00", + "2022-07-31T02:00:00", + "2022-07-31T04:00:00", + "2022-07-31T06:00:00", + "2022-07-31T08:00:00", + "2022-07-31T10:00:00", + "2022-07-31T12:00:00", + "2022-07-31T14:00:00", + "2022-07-31T16:00:00", + "2022-07-31T18:00:00", + "2022-07-31T20:00:00", + "2022-07-31T22:00:00", + "2022-08-01T00:00:00", + "2022-08-01T02:00:00", + "2022-08-01T04:00:00", + "2022-08-01T06:00:00", + "2022-08-01T08:00:00", + "2022-08-01T10:00:00", + "2022-08-01T12:00:00", + "2022-08-01T14:00:00", + "2022-08-01T16:00:00", + "2022-08-01T18:00:00", + "2022-08-01T20:00:00", + "2022-08-01T22:00:00", + "2022-08-02T00:00:00", + "2022-08-02T02:00:00", + "2022-08-02T04:00:00", + "2022-08-02T06:00:00", + "2022-08-02T08:00:00", + "2022-08-02T10:00:00", + "2022-08-02T12:00:00", + "2022-08-02T14:00:00", + "2022-08-02T16:00:00", + "2022-08-02T18:00:00", + "2022-08-02T20:00:00", + "2022-08-02T22:00:00", + "2022-08-03T00:00:00", + "2022-08-03T02:00:00", + "2022-08-03T04:00:00", + "2022-08-03T06:00:00", + "2022-08-03T08:00:00", + "2022-08-03T10:00:00", + "2022-08-03T12:00:00", + "2022-08-03T14:00:00", + "2022-08-03T16:00:00", + "2022-08-03T18:00:00", + "2022-08-03T20:00:00", + "2022-08-03T22:00:00", + "2022-08-04T00:00:00", + "2022-08-04T02:00:00", + "2022-08-04T04:00:00", + "2022-08-04T06:00:00", + "2022-08-04T08:00:00", + "2022-08-04T10:00:00", + "2022-08-04T12:00:00", + "2022-08-04T14:00:00", + "2022-08-04T16:00:00", + "2022-08-04T18:00:00", + "2022-08-04T20:00:00", + "2022-08-04T22:00:00", + "2022-08-05T00:00:00", + "2022-08-05T02:00:00", + "2022-08-05T04:00:00", + "2022-08-05T06:00:00", + "2022-08-05T08:00:00", + "2022-08-05T10:00:00", + "2022-08-05T12:00:00", + "2022-08-05T14:00:00", + "2022-08-05T16:00:00", + "2022-08-05T18:00:00", + "2022-08-05T20:00:00", + "2022-08-05T22:00:00", + "2022-08-06T00:00:00", + "2022-08-06T02:00:00", + "2022-08-06T04:00:00", + "2022-08-06T06:00:00", + "2022-08-06T08:00:00", + "2022-08-06T10:00:00", + "2022-08-06T12:00:00", + "2022-08-06T14:00:00", + "2022-08-06T16:00:00", + "2022-08-06T18:00:00", + "2022-08-06T20:00:00", + "2022-08-06T22:00:00", + "2022-08-07T00:00:00", + "2022-08-07T02:00:00", + "2022-08-07T04:00:00", + "2022-08-07T06:00:00", + "2022-08-07T08:00:00", + "2022-08-07T10:00:00", + "2022-08-07T12:00:00", + "2022-08-07T14:00:00", + "2022-08-07T16:00:00", + "2022-08-07T18:00:00", + "2022-08-07T20:00:00", + "2022-08-07T22:00:00", + "2022-08-08T00:00:00", + "2022-08-08T02:00:00", + "2022-08-08T04:00:00", + "2022-08-08T06:00:00", + "2022-08-08T08:00:00", + "2022-08-08T10:00:00", + "2022-08-08T12:00:00", + "2022-08-08T14:00:00", + "2022-08-08T16:00:00", + "2022-08-08T18:00:00", + "2022-08-08T20:00:00", + "2022-08-08T22:00:00", + "2022-08-09T00:00:00", + "2022-08-09T02:00:00", + "2022-08-09T04:00:00", + "2022-08-09T06:00:00", + "2022-08-09T08:00:00", + "2022-08-09T10:00:00", + "2022-08-09T12:00:00", + "2022-08-09T14:00:00", + "2022-08-09T16:00:00", + "2022-08-09T18:00:00", + "2022-08-09T20:00:00", + "2022-08-09T22:00:00", + "2022-08-10T00:00:00", + "2022-08-10T02:00:00", + "2022-08-10T04:00:00", + "2022-08-10T06:00:00", + "2022-08-10T08:00:00", + "2022-08-10T10:00:00", + "2022-08-10T12:00:00", + "2022-08-10T14:00:00", + "2022-08-10T16:00:00", + "2022-08-10T18:00:00", + "2022-08-10T20:00:00", + "2022-08-10T22:00:00", + "2022-08-11T00:00:00", + "2022-08-11T02:00:00", + "2022-08-11T04:00:00", + "2022-08-11T06:00:00", + "2022-08-11T08:00:00", + "2022-08-11T10:00:00", + "2022-08-11T12:00:00", + "2022-08-11T14:00:00", + "2022-08-11T16:00:00", + "2022-08-11T18:00:00", + "2022-08-11T20:00:00", + "2022-08-11T22:00:00", + "2022-08-12T00:00:00", + "2022-08-12T02:00:00", + "2022-08-12T04:00:00", + "2022-08-12T06:00:00", + "2022-08-12T08:00:00", + "2022-08-12T10:00:00", + "2022-08-12T12:00:00", + "2022-08-12T14:00:00", + "2022-08-12T16:00:00", + "2022-08-12T18:00:00", + "2022-08-12T20:00:00", + "2022-08-12T22:00:00", + "2022-08-13T00:00:00", + "2022-08-13T02:00:00", + "2022-08-13T04:00:00", + "2022-08-13T06:00:00", + "2022-08-13T08:00:00", + "2022-08-13T10:00:00", + "2022-08-13T12:00:00", + "2022-08-13T14:00:00", + "2022-08-13T16:00:00", + "2022-08-13T18:00:00", + "2022-08-13T20:00:00", + "2022-08-13T22:00:00", + "2022-08-14T00:00:00", + "2022-08-14T02:00:00", + "2022-08-14T04:00:00", + "2022-08-14T06:00:00", + "2022-08-14T08:00:00", + "2022-08-14T10:00:00", + "2022-08-14T12:00:00", + "2022-08-14T14:00:00", + "2022-08-14T16:00:00", + "2022-08-14T18:00:00", + "2022-08-14T20:00:00", + "2022-08-14T22:00:00", + "2022-08-15T00:00:00", + "2022-08-15T02:00:00", + "2022-08-15T04:00:00", + "2022-08-15T06:00:00", + "2022-08-15T08:00:00", + "2022-08-15T10:00:00", + "2022-08-15T12:00:00", + "2022-08-15T14:00:00", + "2022-08-15T16:00:00", + "2022-08-15T18:00:00", + "2022-08-15T20:00:00", + "2022-08-15T22:00:00", + "2022-08-16T00:00:00", + "2022-08-16T02:00:00", + "2022-08-16T04:00:00", + "2022-08-16T06:00:00", + "2022-08-16T08:00:00", + "2022-08-16T10:00:00", + "2022-08-16T12:00:00", + "2022-08-16T14:00:00", + "2022-08-16T16:00:00", + "2022-08-16T18:00:00", + "2022-08-16T20:00:00", + "2022-08-16T22:00:00", + "2022-08-17T00:00:00", + "2022-08-17T02:00:00", + "2022-08-17T04:00:00", + "2022-08-17T06:00:00", + "2022-08-17T08:00:00", + "2022-08-17T10:00:00", + "2022-08-17T12:00:00", + "2022-08-17T14:00:00", + "2022-08-17T16:00:00", + "2022-08-17T18:00:00", + "2022-08-17T20:00:00", + "2022-08-17T22:00:00", + "2022-08-18T00:00:00", + "2022-08-18T02:00:00", + "2022-08-18T04:00:00", + "2022-08-18T06:00:00", + "2022-08-18T08:00:00", + "2022-08-18T10:00:00", + "2022-08-18T12:00:00", + "2022-08-18T14:00:00", + "2022-08-18T16:00:00", + "2022-08-18T18:00:00", + "2022-08-18T20:00:00", + "2022-08-18T22:00:00", + "2022-08-19T00:00:00", + "2022-08-19T02:00:00", + "2022-08-19T04:00:00", + "2022-08-19T06:00:00", + "2022-08-19T08:00:00", + "2022-08-19T10:00:00", + "2022-08-19T12:00:00", + "2022-08-19T14:00:00", + "2022-08-19T16:00:00", + "2022-08-19T18:00:00", + "2022-08-19T20:00:00", + "2022-08-19T22:00:00", + "2022-08-20T00:00:00", + "2022-08-20T02:00:00", + "2022-08-20T04:00:00", + "2022-08-20T06:00:00", + "2022-08-20T08:00:00", + "2022-08-20T10:00:00", + "2022-08-20T12:00:00", + "2022-08-20T14:00:00", + "2022-08-20T16:00:00", + "2022-08-20T18:00:00", + "2022-08-20T20:00:00", + "2022-08-20T22:00:00", + "2022-08-21T00:00:00", + "2022-08-21T02:00:00", + "2022-08-21T04:00:00", + "2022-08-21T06:00:00", + "2022-08-21T08:00:00", + "2022-08-21T10:00:00", + "2022-08-21T12:00:00", + "2022-08-21T14:00:00", + "2022-08-21T16:00:00", + "2022-08-21T18:00:00", + "2022-08-21T20:00:00", + "2022-08-21T22:00:00", + "2022-08-22T00:00:00", + "2022-08-22T02:00:00", + "2022-08-22T04:00:00", + "2022-08-22T06:00:00", + "2022-08-22T08:00:00", + "2022-08-22T10:00:00", + "2022-08-22T12:00:00", + "2022-08-22T14:00:00", + "2022-08-22T16:00:00", + "2022-08-22T18:00:00", + "2022-08-22T20:00:00", + "2022-08-22T22:00:00", + "2022-08-23T00:00:00", + "2022-08-23T02:00:00", + "2022-08-23T04:00:00", + "2022-08-23T06:00:00", + "2022-08-23T08:00:00", + "2022-08-23T10:00:00", + "2022-08-23T12:00:00", + "2022-08-23T14:00:00", + "2022-08-23T16:00:00", + "2022-08-23T18:00:00", + "2022-08-23T20:00:00", + "2022-08-23T22:00:00", + "2022-08-24T00:00:00", + "2022-08-24T02:00:00", + "2022-08-24T04:00:00", + "2022-08-24T06:00:00", + "2022-08-24T08:00:00", + "2022-08-24T10:00:00", + "2022-08-24T12:00:00", + "2022-08-24T14:00:00", + "2022-08-24T16:00:00", + "2022-08-24T18:00:00", + "2022-08-24T20:00:00", + "2022-08-24T22:00:00", + "2022-08-25T00:00:00", + "2022-08-25T02:00:00", + "2022-08-25T04:00:00", + "2022-08-25T06:00:00", + "2022-08-25T08:00:00", + "2022-08-25T10:00:00", + "2022-08-25T12:00:00", + "2022-08-25T14:00:00", + "2022-08-25T16:00:00", + "2022-08-25T18:00:00", + "2022-08-25T20:00:00", + "2022-08-25T22:00:00", + "2022-08-26T00:00:00", + "2022-08-26T02:00:00", + "2022-08-26T04:00:00", + "2022-08-26T06:00:00", + "2022-08-26T08:00:00", + "2022-08-26T10:00:00", + "2022-08-26T12:00:00", + "2022-08-26T14:00:00", + "2022-08-26T16:00:00", + "2022-08-26T18:00:00", + "2022-08-26T20:00:00", + "2022-08-26T22:00:00", + "2022-08-27T00:00:00", + "2022-08-27T02:00:00", + "2022-08-27T04:00:00", + "2022-08-27T06:00:00", + "2022-08-27T08:00:00", + "2022-08-27T10:00:00", + "2022-08-27T12:00:00", + "2022-08-27T14:00:00", + "2022-08-27T16:00:00", + "2022-08-27T18:00:00", + "2022-08-27T20:00:00", + "2022-08-27T22:00:00", + "2022-08-28T00:00:00", + "2022-08-28T02:00:00", + "2022-08-28T04:00:00", + "2022-08-28T06:00:00", + "2022-08-28T08:00:00", + "2022-08-28T10:00:00", + "2022-08-28T12:00:00", + "2022-08-28T14:00:00", + "2022-08-28T16:00:00", + "2022-08-28T18:00:00", + "2022-08-28T20:00:00", + "2022-08-28T22:00:00", + "2022-08-29T00:00:00", + "2022-08-29T02:00:00", + "2022-08-29T04:00:00", + "2022-08-29T06:00:00", + "2022-08-29T08:00:00", + "2022-08-29T10:00:00", + "2022-08-29T12:00:00", + "2022-08-29T14:00:00", + "2022-08-29T16:00:00", + "2022-08-29T18:00:00", + "2022-08-29T20:00:00", + "2022-08-29T22:00:00", + "2022-08-30T00:00:00", + "2022-08-30T02:00:00", + "2022-08-30T04:00:00", + "2022-08-30T06:00:00", + "2022-08-30T08:00:00", + "2022-08-30T10:00:00", + "2022-08-30T12:00:00", + "2022-08-30T14:00:00", + "2022-08-30T16:00:00", + "2022-08-30T18:00:00", + "2022-08-30T20:00:00", + "2022-08-30T22:00:00", + "2022-08-31T00:00:00", + "2022-08-31T02:00:00", + "2022-08-31T04:00:00", + "2022-08-31T06:00:00", + "2022-08-31T08:00:00", + "2022-08-31T10:00:00", + "2022-08-31T12:00:00", + "2022-08-31T14:00:00", + "2022-08-31T16:00:00", + "2022-08-31T18:00:00", + "2022-08-31T20:00:00", + "2022-08-31T22:00:00", + "2022-09-01T00:00:00", + "2022-09-01T02:00:00", + "2022-09-01T04:00:00", + "2022-09-01T06:00:00", + "2022-09-01T08:00:00", + "2022-09-01T10:00:00", + "2022-09-01T12:00:00", + "2022-09-01T14:00:00", + "2022-09-01T16:00:00", + "2022-09-01T18:00:00", + "2022-09-01T20:00:00", + "2022-09-01T22:00:00", + "2022-09-02T00:00:00", + "2022-09-02T02:00:00", + "2022-09-02T04:00:00", + "2022-09-02T06:00:00", + "2022-09-02T08:00:00", + "2022-09-02T10:00:00", + "2022-09-02T12:00:00", + "2022-09-02T14:00:00", + "2022-09-02T16:00:00", + "2022-09-02T18:00:00", + "2022-09-02T20:00:00", + "2022-09-02T22:00:00", + "2022-09-03T00:00:00", + "2022-09-03T02:00:00", + "2022-09-03T04:00:00", + "2022-09-03T06:00:00", + "2022-09-03T08:00:00", + "2022-09-03T10:00:00", + "2022-09-03T12:00:00", + "2022-09-03T14:00:00", + "2022-09-03T16:00:00", + "2022-09-03T18:00:00", + "2022-09-03T20:00:00", + "2022-09-03T22:00:00", + "2022-09-04T00:00:00", + "2022-09-04T02:00:00", + "2022-09-04T04:00:00", + "2022-09-04T06:00:00", + "2022-09-04T08:00:00", + "2022-09-04T10:00:00", + "2022-09-04T12:00:00", + "2022-09-04T14:00:00", + "2022-09-04T16:00:00", + "2022-09-04T18:00:00", + "2022-09-04T20:00:00", + "2022-09-04T22:00:00", + "2022-09-05T00:00:00", + "2022-09-05T02:00:00", + "2022-09-05T04:00:00", + "2022-09-05T06:00:00", + "2022-09-05T08:00:00", + "2022-09-05T10:00:00", + "2022-09-05T12:00:00", + "2022-09-05T14:00:00", + "2022-09-05T16:00:00", + "2022-09-05T18:00:00", + "2022-09-05T20:00:00", + "2022-09-05T22:00:00", + "2022-09-06T00:00:00", + "2022-09-06T02:00:00", + "2022-09-06T04:00:00", + "2022-09-06T06:00:00", + "2022-09-06T08:00:00", + "2022-09-06T10:00:00", + "2022-09-06T12:00:00", + "2022-09-06T14:00:00", + "2022-09-06T16:00:00", + "2022-09-06T18:00:00", + "2022-09-06T20:00:00", + "2022-09-06T22:00:00", + "2022-09-07T00:00:00", + "2022-09-07T02:00:00", + "2022-09-07T04:00:00", + "2022-09-07T06:00:00", + "2022-09-07T08:00:00", + "2022-09-07T10:00:00", + "2022-09-07T12:00:00", + "2022-09-07T14:00:00", + "2022-09-07T16:00:00", + "2022-09-07T18:00:00", + "2022-09-07T20:00:00", + "2022-09-07T22:00:00", + "2022-09-08T00:00:00", + "2022-09-08T02:00:00", + "2022-09-08T04:00:00", + "2022-09-08T06:00:00", + "2022-09-08T08:00:00", + "2022-09-08T10:00:00", + "2022-09-08T12:00:00", + "2022-09-08T14:00:00", + "2022-09-08T16:00:00", + "2022-09-08T18:00:00", + "2022-09-08T20:00:00", + "2022-09-08T22:00:00", + "2022-09-09T00:00:00", + "2022-09-09T02:00:00", + "2022-09-09T04:00:00", + "2022-09-09T06:00:00", + "2022-09-09T08:00:00", + "2022-09-09T10:00:00", + "2022-09-09T12:00:00", + "2022-09-09T14:00:00", + "2022-09-09T16:00:00", + "2022-09-09T18:00:00", + "2022-09-09T20:00:00", + "2022-09-09T22:00:00", + "2022-09-10T00:00:00", + "2022-09-10T02:00:00", + "2022-09-10T04:00:00", + "2022-09-10T06:00:00", + "2022-09-10T08:00:00", + "2022-09-10T10:00:00", + "2022-09-10T12:00:00", + "2022-09-10T14:00:00", + "2022-09-10T16:00:00", + "2022-09-10T18:00:00", + "2022-09-10T20:00:00", + "2022-09-10T22:00:00", + "2022-09-11T00:00:00", + "2022-09-11T02:00:00", + "2022-09-11T04:00:00", + "2022-09-11T06:00:00", + "2022-09-11T08:00:00", + "2022-09-11T10:00:00", + "2022-09-11T12:00:00", + "2022-09-11T14:00:00", + "2022-09-11T16:00:00", + "2022-09-11T18:00:00", + "2022-09-11T20:00:00", + "2022-09-11T22:00:00", + "2022-09-12T00:00:00", + "2022-09-12T02:00:00", + "2022-09-12T04:00:00", + "2022-09-12T06:00:00", + "2022-09-12T08:00:00", + "2022-09-12T10:00:00", + "2022-09-12T12:00:00", + "2022-09-12T14:00:00", + "2022-09-12T16:00:00", + "2022-09-12T18:00:00", + "2022-09-12T20:00:00", + "2022-09-12T22:00:00", + "2022-09-13T00:00:00", + "2022-09-13T02:00:00", + "2022-09-13T04:00:00", + "2022-09-13T06:00:00", + "2022-09-13T08:00:00", + "2022-09-13T10:00:00", + "2022-09-13T12:00:00", + "2022-09-13T14:00:00", + "2022-09-13T16:00:00", + "2022-09-13T18:00:00", + "2022-09-13T20:00:00", + "2022-09-13T22:00:00", + "2022-09-14T00:00:00", + "2022-09-14T02:00:00", + "2022-09-14T04:00:00", + "2022-09-14T06:00:00", + "2022-09-14T08:00:00", + "2022-09-14T10:00:00", + "2022-09-14T12:00:00", + "2022-09-14T14:00:00", + "2022-09-14T16:00:00", + "2022-09-14T18:00:00", + "2022-09-14T20:00:00", + "2022-09-14T22:00:00", + "2022-09-15T00:00:00", + "2022-09-15T02:00:00", + "2022-09-15T04:00:00", + "2022-09-15T06:00:00", + "2022-09-15T08:00:00", + "2022-09-15T10:00:00", + "2022-09-15T12:00:00", + "2022-09-15T14:00:00", + "2022-09-15T16:00:00", + "2022-09-15T18:00:00", + "2022-09-15T20:00:00", + "2022-09-15T22:00:00", + "2022-09-16T00:00:00", + "2022-09-16T02:00:00", + "2022-09-16T04:00:00", + "2022-09-16T06:00:00", + "2022-09-16T08:00:00", + "2022-09-16T10:00:00", + "2022-09-16T12:00:00", + "2022-09-16T14:00:00", + "2022-09-16T16:00:00", + "2022-09-16T18:00:00", + "2022-09-16T20:00:00", + "2022-09-16T22:00:00", + "2022-09-17T00:00:00", + "2022-09-17T02:00:00", + "2022-09-17T04:00:00", + "2022-09-17T06:00:00", + "2022-09-17T08:00:00", + "2022-09-17T10:00:00", + "2022-09-17T12:00:00", + "2022-09-17T14:00:00", + "2022-09-17T16:00:00", + "2022-09-17T18:00:00", + "2022-09-17T20:00:00", + "2022-09-17T22:00:00", + "2022-09-18T00:00:00", + "2022-09-18T02:00:00", + "2022-09-18T04:00:00", + "2022-09-18T06:00:00", + "2022-09-18T08:00:00", + "2022-09-18T10:00:00", + "2022-09-18T12:00:00", + "2022-09-18T14:00:00", + "2022-09-18T16:00:00", + "2022-09-18T18:00:00", + "2022-09-18T20:00:00", + "2022-09-18T22:00:00", + "2022-09-19T00:00:00", + "2022-09-19T02:00:00", + "2022-09-19T04:00:00", + "2022-09-19T06:00:00", + "2022-09-19T08:00:00", + "2022-09-19T10:00:00", + "2022-09-19T12:00:00", + "2022-09-19T14:00:00", + "2022-09-19T16:00:00", + "2022-09-19T18:00:00", + "2022-09-19T20:00:00", + "2022-09-19T22:00:00", + "2022-09-20T00:00:00", + "2022-09-20T02:00:00", + "2022-09-20T04:00:00", + "2022-09-20T06:00:00", + "2022-09-20T08:00:00", + "2022-09-20T10:00:00", + "2022-09-20T12:00:00", + "2022-09-20T14:00:00", + "2022-09-20T16:00:00", + "2022-09-20T18:00:00", + "2022-09-20T20:00:00", + "2022-09-20T22:00:00", + "2022-09-21T00:00:00", + "2022-09-21T02:00:00", + "2022-09-21T04:00:00", + "2022-09-21T06:00:00", + "2022-09-21T08:00:00", + "2022-09-21T10:00:00", + "2022-09-21T12:00:00", + "2022-09-21T14:00:00", + "2022-09-21T16:00:00", + "2022-09-21T18:00:00", + "2022-09-21T20:00:00", + "2022-09-21T22:00:00", + "2022-09-22T00:00:00", + "2022-09-22T02:00:00", + "2022-09-22T04:00:00", + "2022-09-22T06:00:00", + "2022-09-22T08:00:00", + "2022-09-22T10:00:00", + "2022-09-22T12:00:00", + "2022-09-22T14:00:00", + "2022-09-22T16:00:00", + "2022-09-22T18:00:00", + "2022-09-22T20:00:00", + "2022-09-22T22:00:00", + "2022-09-23T00:00:00", + "2022-09-23T02:00:00", + "2022-09-23T04:00:00", + "2022-09-23T06:00:00", + "2022-09-23T08:00:00", + "2022-09-23T10:00:00", + "2022-09-23T12:00:00", + "2022-09-23T14:00:00", + "2022-09-23T16:00:00", + "2022-09-23T18:00:00", + "2022-09-23T20:00:00", + "2022-09-23T22:00:00", + "2022-09-24T00:00:00", + "2022-09-24T02:00:00", + "2022-09-24T04:00:00", + "2022-09-24T06:00:00", + "2022-09-24T08:00:00", + "2022-09-24T10:00:00", + "2022-09-24T12:00:00", + "2022-09-24T14:00:00", + "2022-09-24T16:00:00", + "2022-09-24T18:00:00", + "2022-09-24T20:00:00", + "2022-09-24T22:00:00", + "2022-09-25T00:00:00", + "2022-09-25T02:00:00", + "2022-09-25T04:00:00", + "2022-09-25T06:00:00", + "2022-09-25T08:00:00", + "2022-09-25T10:00:00", + "2022-09-25T12:00:00", + "2022-09-25T14:00:00", + "2022-09-25T16:00:00", + "2022-09-25T18:00:00", + "2022-09-25T20:00:00", + "2022-09-25T22:00:00", + "2022-09-26T00:00:00", + "2022-09-26T02:00:00", + "2022-09-26T04:00:00", + "2022-09-26T06:00:00", + "2022-09-26T08:00:00", + "2022-09-26T10:00:00", + "2022-09-26T12:00:00", + "2022-09-26T14:00:00", + "2022-09-26T16:00:00", + "2022-09-26T18:00:00", + "2022-09-26T20:00:00", + "2022-09-26T22:00:00", + "2022-09-27T00:00:00", + "2022-09-27T02:00:00", + "2022-09-27T04:00:00", + "2022-09-27T06:00:00", + "2022-09-27T08:00:00", + "2022-09-27T10:00:00", + "2022-09-27T12:00:00", + "2022-09-27T14:00:00", + "2022-09-27T16:00:00", + "2022-09-27T18:00:00", + "2022-09-27T20:00:00", + "2022-09-27T22:00:00", + "2022-09-28T00:00:00", + "2022-09-28T02:00:00", + "2022-09-28T04:00:00", + "2022-09-28T06:00:00", + "2022-09-28T08:00:00", + "2022-09-28T10:00:00", + "2022-09-28T12:00:00", + "2022-09-28T14:00:00", + "2022-09-28T16:00:00", + "2022-09-28T18:00:00", + "2022-09-28T20:00:00", + "2022-09-28T22:00:00", + "2022-09-29T00:00:00", + "2022-09-29T02:00:00", + "2022-09-29T04:00:00", + "2022-09-29T06:00:00", + "2022-09-29T08:00:00", + "2022-09-29T10:00:00", + "2022-09-29T12:00:00", + "2022-09-29T14:00:00", + "2022-09-29T16:00:00", + "2022-09-29T18:00:00", + "2022-09-29T20:00:00", + "2022-09-29T22:00:00", + "2022-09-30T00:00:00", + "2022-09-30T02:00:00", + "2022-09-30T04:00:00", + "2022-09-30T06:00:00", + "2022-09-30T08:00:00", + "2022-09-30T10:00:00", + "2022-09-30T12:00:00", + "2022-09-30T14:00:00", + "2022-09-30T16:00:00", + "2022-09-30T18:00:00", + "2022-09-30T20:00:00", + "2022-09-30T22:00:00", + "2022-10-01T00:00:00", + "2022-10-01T02:00:00", + "2022-10-01T04:00:00", + "2022-10-01T06:00:00", + "2022-10-01T08:00:00", + "2022-10-01T10:00:00", + "2022-10-01T12:00:00", + "2022-10-01T14:00:00", + "2022-10-01T16:00:00", + "2022-10-01T18:00:00", + "2022-10-01T20:00:00", + "2022-10-01T22:00:00", + "2022-10-02T00:00:00", + "2022-10-02T02:00:00", + "2022-10-02T04:00:00", + "2022-10-02T06:00:00", + "2022-10-02T08:00:00", + "2022-10-02T10:00:00", + "2022-10-02T12:00:00", + "2022-10-02T14:00:00", + "2022-10-02T16:00:00", + "2022-10-02T18:00:00", + "2022-10-02T20:00:00", + "2022-10-02T22:00:00", + "2022-10-03T00:00:00", + "2022-10-03T02:00:00", + "2022-10-03T04:00:00", + "2022-10-03T06:00:00", + "2022-10-03T08:00:00", + "2022-10-03T10:00:00", + "2022-10-03T12:00:00", + "2022-10-03T14:00:00", + "2022-10-03T16:00:00", + "2022-10-03T18:00:00", + "2022-10-03T20:00:00", + "2022-10-03T22:00:00", + "2022-10-04T00:00:00", + "2022-10-04T02:00:00", + "2022-10-04T04:00:00", + "2022-10-04T06:00:00", + "2022-10-04T08:00:00", + "2022-10-04T10:00:00", + "2022-10-04T12:00:00", + "2022-10-04T14:00:00", + "2022-10-04T16:00:00", + "2022-10-04T18:00:00", + "2022-10-04T20:00:00", + "2022-10-04T22:00:00", + "2022-10-05T00:00:00", + "2022-10-05T02:00:00", + "2022-10-05T04:00:00", + "2022-10-05T06:00:00", + "2022-10-05T08:00:00", + "2022-10-05T10:00:00", + "2022-10-05T12:00:00", + "2022-10-05T14:00:00", + "2022-10-05T16:00:00", + "2022-10-05T18:00:00", + "2022-10-05T20:00:00", + "2022-10-05T22:00:00", + "2022-10-06T00:00:00", + "2022-10-06T02:00:00", + "2022-10-06T04:00:00", + "2022-10-06T06:00:00", + "2022-10-06T08:00:00", + "2022-10-06T10:00:00", + "2022-10-06T12:00:00", + "2022-10-06T14:00:00", + "2022-10-06T16:00:00", + "2022-10-06T18:00:00", + "2022-10-06T20:00:00", + "2022-10-06T22:00:00", + "2022-10-07T00:00:00", + "2022-10-07T02:00:00", + "2022-10-07T04:00:00", + "2022-10-07T06:00:00", + "2022-10-07T08:00:00", + "2022-10-07T10:00:00", + "2022-10-07T12:00:00", + "2022-10-07T14:00:00", + "2022-10-07T16:00:00", + "2022-10-07T18:00:00", + "2022-10-07T20:00:00", + "2022-10-07T22:00:00", + "2022-10-08T00:00:00", + "2022-10-08T04:00:00", + "2022-10-08T06:00:00", + "2022-10-08T08:00:00", + "2022-10-08T10:00:00", + "2022-10-08T12:00:00", + "2022-10-08T14:00:00", + "2022-10-08T16:00:00", + "2022-10-08T18:00:00", + "2022-10-08T20:00:00", + "2022-10-08T22:00:00", + "2022-10-09T00:00:00", + "2022-10-09T02:00:00", + "2022-10-09T04:00:00", + "2022-10-09T06:00:00", + "2022-10-09T08:00:00", + "2022-10-09T10:00:00", + "2022-10-09T12:00:00", + "2022-10-09T14:00:00", + "2022-10-09T16:00:00", + "2022-10-09T18:00:00", + "2022-10-09T20:00:00", + "2022-10-09T22:00:00", + "2022-10-10T00:00:00", + "2022-10-10T02:00:00", + "2022-10-10T04:00:00", + "2022-10-10T06:00:00", + "2022-10-10T08:00:00", + "2022-10-10T10:00:00", + "2022-10-10T12:00:00", + "2022-10-10T14:00:00", + "2022-10-10T16:00:00", + "2022-10-10T18:00:00", + "2022-10-10T20:00:00", + "2022-10-10T22:00:00", + "2022-10-11T00:00:00", + "2022-10-11T02:00:00", + "2022-10-11T04:00:00", + "2022-10-11T06:00:00", + "2022-10-11T08:00:00", + "2022-10-11T10:00:00", + "2022-10-11T12:00:00", + "2022-10-11T14:00:00", + "2022-10-11T16:00:00", + "2022-10-11T18:00:00", + "2022-10-11T20:00:00", + "2022-10-11T22:00:00", + "2022-10-12T00:00:00", + "2022-10-12T02:00:00", + "2022-10-12T04:00:00", + "2022-10-12T06:00:00", + "2022-10-12T08:00:00", + "2022-10-12T10:00:00", + "2022-10-12T12:00:00", + "2022-10-12T14:00:00", + "2022-10-12T16:00:00", + "2022-10-12T18:00:00", + "2022-10-12T20:00:00", + "2022-10-12T22:00:00", + "2022-10-13T00:00:00", + "2022-10-13T02:00:00", + "2022-10-13T04:00:00", + "2022-10-13T06:00:00", + "2022-10-13T08:00:00", + "2022-10-13T10:00:00", + "2022-10-13T12:00:00", + "2022-10-13T14:00:00", + "2022-10-13T16:00:00", + "2022-10-13T18:00:00", + "2022-10-13T20:00:00", + "2022-10-13T22:00:00", + "2022-10-14T00:00:00", + "2022-10-14T02:00:00", + "2022-10-14T04:00:00", + "2022-10-14T06:00:00", + "2022-10-14T08:00:00", + "2022-10-14T10:00:00", + "2022-10-14T12:00:00", + "2022-10-14T14:00:00", + "2022-10-14T16:00:00", + "2022-10-14T18:00:00", + "2022-10-14T20:00:00", + "2022-10-14T22:00:00", + "2022-10-15T00:00:00", + "2022-10-15T02:00:00", + "2022-10-15T04:00:00", + "2022-10-15T06:00:00", + "2022-10-15T08:00:00", + "2022-10-15T10:00:00", + "2022-10-15T12:00:00", + "2022-10-15T14:00:00", + "2022-10-15T16:00:00", + "2022-10-15T18:00:00", + "2022-10-15T20:00:00", + "2022-10-15T22:00:00", + "2022-10-16T00:00:00", + "2022-10-16T02:00:00", + "2022-10-16T04:00:00", + "2022-10-16T06:00:00", + "2022-10-16T08:00:00", + "2022-10-16T10:00:00", + "2022-10-16T12:00:00", + "2022-10-16T14:00:00", + "2022-10-16T16:00:00", + "2022-10-16T18:00:00", + "2022-10-16T20:00:00", + "2022-10-16T22:00:00", + "2022-10-17T00:00:00", + "2022-10-17T02:00:00", + "2022-10-17T04:00:00", + "2022-10-17T06:00:00", + "2022-10-17T08:00:00", + "2022-10-17T10:00:00", + "2022-10-17T12:00:00", + "2022-10-17T14:00:00", + "2022-10-17T16:00:00", + "2022-10-17T18:00:00", + "2022-10-17T20:00:00", + "2022-10-17T22:00:00", + "2022-10-18T00:00:00", + "2022-10-18T02:00:00", + "2022-10-18T04:00:00", + "2022-10-18T06:00:00", + "2022-10-18T08:00:00", + "2022-10-18T10:00:00", + "2022-10-18T12:00:00", + "2022-10-18T14:00:00", + "2022-10-18T16:00:00", + "2022-10-18T18:00:00", + "2022-10-18T20:00:00", + "2022-10-18T22:00:00", + "2022-10-19T00:00:00", + "2022-10-19T02:00:00", + "2022-10-19T04:00:00", + "2022-10-19T06:00:00", + "2022-10-19T08:00:00", + "2022-10-19T10:00:00", + "2022-10-19T12:00:00", + "2022-10-19T14:00:00", + "2022-10-19T16:00:00", + "2022-10-19T18:00:00", + "2022-10-19T20:00:00", + "2022-10-19T22:00:00", + "2022-10-20T00:00:00", + "2022-10-20T02:00:00", + "2022-10-20T04:00:00", + "2022-10-20T06:00:00", + "2022-10-20T08:00:00", + "2022-10-20T10:00:00", + "2022-10-20T12:00:00", + "2022-10-20T14:00:00", + "2022-10-20T16:00:00", + "2022-10-20T18:00:00", + "2022-10-20T20:00:00", + "2022-10-20T22:00:00", + "2022-10-21T00:00:00", + "2022-10-21T02:00:00", + "2022-10-21T04:00:00", + "2022-10-21T06:00:00", + "2022-10-21T08:00:00", + "2022-10-21T10:00:00", + "2022-10-21T12:00:00", + "2022-10-21T14:00:00", + "2022-10-21T16:00:00", + "2022-10-21T18:00:00", + "2022-10-21T20:00:00", + "2022-10-21T22:00:00", + "2022-10-22T00:00:00", + "2022-10-22T02:00:00", + "2022-10-22T04:00:00", + "2022-10-22T06:00:00", + "2022-10-22T08:00:00", + "2022-10-22T10:00:00", + "2022-10-22T12:00:00", + "2022-10-22T14:00:00", + "2022-10-22T16:00:00", + "2022-10-22T18:00:00", + "2022-10-22T20:00:00", + "2022-10-22T22:00:00", + "2022-10-23T00:00:00", + "2022-10-23T02:00:00", + "2022-10-23T04:00:00", + "2022-10-23T06:00:00", + "2022-10-23T08:00:00", + "2022-10-23T10:00:00", + "2022-10-23T12:00:00", + "2022-10-23T14:00:00", + "2022-10-23T16:00:00", + "2022-10-23T18:00:00", + "2022-10-23T20:00:00", + "2022-10-23T22:00:00", + "2022-10-24T00:00:00", + "2022-10-24T02:00:00", + "2022-10-24T04:00:00", + "2022-10-24T06:00:00", + "2022-10-24T08:00:00", + "2022-10-24T10:00:00", + "2022-10-24T12:00:00", + "2022-10-24T14:00:00", + "2022-10-24T16:00:00", + "2022-10-24T18:00:00", + "2022-10-24T20:00:00", + "2022-10-24T22:00:00", + "2022-10-25T00:00:00", + "2022-10-25T02:00:00", + "2022-10-25T04:00:00", + "2022-10-25T06:00:00", + "2022-10-25T08:00:00", + "2022-10-25T10:00:00", + "2022-10-25T12:00:00", + "2022-10-25T14:00:00", + "2022-10-25T16:00:00", + "2022-10-25T18:00:00", + "2022-10-25T20:00:00", + "2022-10-25T22:00:00", + "2022-10-26T00:00:00", + "2022-10-26T02:00:00", + "2022-10-26T04:00:00", + "2022-10-26T06:00:00", + "2022-10-26T08:00:00", + "2022-10-26T10:00:00", + "2022-10-26T12:00:00", + "2022-10-26T14:00:00", + "2022-10-26T16:00:00", + "2022-10-26T18:00:00", + "2022-10-26T20:00:00", + "2022-10-26T22:00:00", + "2022-10-27T00:00:00", + "2022-10-27T02:00:00", + "2022-10-27T04:00:00", + "2022-10-27T06:00:00", + "2022-10-27T08:00:00", + "2022-10-27T10:00:00", + "2022-10-27T12:00:00", + "2022-10-27T14:00:00", + "2022-10-27T16:00:00", + "2022-10-27T18:00:00", + "2022-10-27T20:00:00", + "2022-10-27T22:00:00", + "2022-10-28T00:00:00", + "2022-10-28T02:00:00", + "2022-10-28T04:00:00", + "2022-10-28T06:00:00", + "2022-10-28T08:00:00", + "2022-10-28T10:00:00", + "2022-10-28T12:00:00", + "2022-10-28T14:00:00", + "2022-10-28T16:00:00", + "2022-10-28T18:00:00", + "2022-10-28T20:00:00", + "2022-10-28T22:00:00", + "2022-10-29T00:00:00", + "2022-10-29T02:00:00", + "2022-10-29T04:00:00", + "2022-10-29T06:00:00", + "2022-10-29T08:00:00", + "2022-10-29T10:00:00", + "2022-10-29T12:00:00", + "2022-10-29T14:00:00", + "2022-10-29T16:00:00", + "2022-10-29T18:00:00", + "2022-10-29T20:00:00", + "2022-10-29T22:00:00", + "2022-10-30T00:00:00", + "2022-10-30T02:00:00", + "2022-10-30T04:00:00", + "2022-10-30T06:00:00", + "2022-10-30T08:00:00", + "2022-10-30T10:00:00", + "2022-10-30T12:00:00", + "2022-10-30T14:00:00", + "2022-10-30T16:00:00", + "2022-10-30T18:00:00", + "2022-10-30T20:00:00", + "2022-10-30T22:00:00", + "2022-10-31T00:00:00", + "2022-10-31T02:00:00", + "2022-10-31T04:00:00", + "2022-10-31T06:00:00", + "2022-10-31T08:00:00", + "2022-10-31T10:00:00", + "2022-10-31T12:00:00", + "2022-10-31T14:00:00", + "2022-10-31T16:00:00", + "2022-10-31T18:00:00", + "2022-10-31T20:00:00", + "2022-10-31T22:00:00", + "2022-11-01T00:00:00", + "2022-11-01T02:00:00", + "2022-11-01T04:00:00", + "2022-11-01T06:00:00", + "2022-11-01T08:00:00", + "2022-11-01T10:00:00", + "2022-11-01T12:00:00", + "2022-11-01T14:00:00", + "2022-11-01T16:00:00", + "2022-11-01T18:00:00", + "2022-11-01T20:00:00", + "2022-11-01T22:00:00", + "2022-11-02T00:00:00", + "2022-11-02T02:00:00", + "2022-11-02T04:00:00", + "2022-11-02T06:00:00", + "2022-11-02T08:00:00", + "2022-11-02T10:00:00", + "2022-11-02T12:00:00", + "2022-11-02T14:00:00", + "2022-11-02T16:00:00", + "2022-11-02T18:00:00", + "2022-11-02T20:00:00", + "2022-11-02T22:00:00", + "2022-11-03T00:00:00", + "2022-11-03T02:00:00", + "2022-11-03T04:00:00", + "2022-11-03T06:00:00", + "2022-11-03T08:00:00", + "2022-11-03T10:00:00", + "2022-11-03T12:00:00", + "2022-11-03T14:00:00", + "2022-11-03T16:00:00", + "2022-11-03T18:00:00", + "2022-11-03T20:00:00", + "2022-11-03T22:00:00", + "2022-11-04T00:00:00", + "2022-11-04T02:00:00", + "2022-11-04T04:00:00", + "2022-11-04T06:00:00", + "2022-11-04T08:00:00", + "2022-11-04T10:00:00", + "2022-11-04T12:00:00", + "2022-11-04T14:00:00", + "2022-11-04T16:00:00", + "2022-11-04T18:00:00", + "2022-11-04T20:00:00", + "2022-11-04T22:00:00", + "2022-11-05T00:00:00", + "2022-11-05T02:00:00", + "2022-11-05T04:00:00", + "2022-11-05T06:00:00", + "2022-11-05T08:00:00", + "2022-11-05T10:00:00", + "2022-11-05T12:00:00", + "2022-11-05T14:00:00", + "2022-11-05T16:00:00", + "2022-11-05T18:00:00", + "2022-11-05T20:00:00", + "2022-11-05T22:00:00", + "2022-11-06T00:00:00", + "2022-11-06T02:00:00", + "2022-11-06T04:00:00", + "2022-11-06T06:00:00", + "2022-11-06T08:00:00", + "2022-11-06T10:00:00", + "2022-11-06T12:00:00", + "2022-11-06T14:00:00", + "2022-11-06T16:00:00", + "2022-11-06T18:00:00", + "2022-11-06T20:00:00", + "2022-11-06T22:00:00", + "2022-11-07T00:00:00", + "2022-11-07T02:00:00", + "2022-11-07T04:00:00", + "2022-11-07T06:00:00", + "2022-11-07T08:00:00", + "2022-11-07T10:00:00", + "2022-11-07T12:00:00", + "2022-11-07T14:00:00", + "2022-11-07T16:00:00", + "2022-11-07T18:00:00", + "2022-11-07T20:00:00", + "2022-11-07T22:00:00", + "2022-11-08T00:00:00", + "2022-11-08T02:00:00", + "2022-11-08T04:00:00", + "2022-11-08T06:00:00", + "2022-11-08T08:00:00", + "2022-11-08T10:00:00", + "2022-11-08T12:00:00", + "2022-11-08T14:00:00", + "2022-11-08T16:00:00", + "2022-11-08T18:00:00", + "2022-11-08T20:00:00", + "2022-11-08T22:00:00", + "2022-11-09T00:00:00", + "2022-11-09T02:00:00", + "2022-11-09T04:00:00", + "2022-11-09T06:00:00", + "2022-11-09T08:00:00", + "2022-11-09T10:00:00", + "2022-11-09T12:00:00", + "2022-11-09T14:00:00", + "2022-11-09T16:00:00", + "2022-11-09T18:00:00", + "2022-11-09T20:00:00", + "2022-11-09T22:00:00", + "2022-11-10T00:00:00", + "2022-11-10T02:00:00", + "2022-11-10T04:00:00", + "2022-11-10T06:00:00", + "2022-11-10T08:00:00", + "2022-11-10T10:00:00", + "2022-11-10T12:00:00", + "2022-11-10T14:00:00", + "2022-11-10T16:00:00", + "2022-11-10T18:00:00", + "2022-11-10T20:00:00", + "2022-11-10T22:00:00", + "2022-11-11T00:00:00", + "2022-11-11T02:00:00", + "2022-11-11T04:00:00", + "2022-11-11T06:00:00", + "2022-11-11T08:00:00", + "2022-11-11T10:00:00", + "2022-11-11T12:00:00", + "2022-11-11T14:00:00", + "2022-11-11T16:00:00", + "2022-11-11T18:00:00", + "2022-11-11T20:00:00", + "2022-11-11T22:00:00", + "2022-11-12T00:00:00", + "2022-11-12T02:00:00", + "2022-11-12T04:00:00", + "2022-11-12T06:00:00", + "2022-11-12T08:00:00", + "2022-11-12T10:00:00", + "2022-11-12T12:00:00", + "2022-11-12T14:00:00", + "2022-11-12T16:00:00", + "2022-11-12T18:00:00", + "2022-11-12T20:00:00", + "2022-11-12T22:00:00", + "2022-11-13T00:00:00", + "2022-11-13T02:00:00", + "2022-11-13T04:00:00", + "2022-11-13T06:00:00", + "2022-11-13T08:00:00", + "2022-11-13T10:00:00", + "2022-11-13T12:00:00", + "2022-11-13T14:00:00", + "2022-11-13T16:00:00", + "2022-11-13T18:00:00", + "2022-11-13T20:00:00", + "2022-11-13T22:00:00", + "2022-11-14T00:00:00", + "2022-11-14T02:00:00", + "2022-11-14T04:00:00", + "2022-11-14T06:00:00", + "2022-11-14T08:00:00", + "2022-11-14T10:00:00", + "2022-11-14T12:00:00", + "2022-11-14T14:00:00", + "2022-11-14T16:00:00", + "2022-11-14T18:00:00", + "2022-11-14T20:00:00", + "2022-11-14T22:00:00", + "2022-11-15T00:00:00", + "2022-11-15T02:00:00", + "2022-11-15T04:00:00", + "2022-11-15T06:00:00", + "2022-11-15T08:00:00", + "2022-11-15T10:00:00", + "2022-11-15T12:00:00", + "2022-11-15T14:00:00", + "2022-11-15T16:00:00", + "2022-11-15T18:00:00", + "2022-11-15T20:00:00", + "2022-11-15T22:00:00", + "2022-11-16T00:00:00", + "2022-11-16T02:00:00", + "2022-11-16T04:00:00", + "2022-11-16T06:00:00", + "2022-11-16T08:00:00", + "2022-11-16T10:00:00", + "2022-11-16T12:00:00", + "2022-11-16T14:00:00", + "2022-11-16T16:00:00", + "2022-11-16T18:00:00", + "2022-11-16T20:00:00", + "2022-11-16T22:00:00", + "2022-11-17T00:00:00", + "2022-11-17T02:00:00", + "2022-11-17T04:00:00", + "2022-11-17T06:00:00", + "2022-11-17T08:00:00", + "2022-11-17T10:00:00", + "2022-11-17T12:00:00", + "2022-11-17T14:00:00", + "2022-11-17T16:00:00", + "2022-11-17T18:00:00", + "2022-11-17T20:00:00", + "2022-11-17T22:00:00", + "2022-11-18T00:00:00", + "2022-11-18T02:00:00", + "2022-11-18T04:00:00", + "2022-11-18T06:00:00", + "2022-11-18T08:00:00", + "2022-11-18T10:00:00", + "2022-11-18T12:00:00", + "2022-11-18T14:00:00", + "2022-11-18T16:00:00", + "2022-11-18T18:00:00", + "2022-11-18T20:00:00", + "2022-11-18T22:00:00", + "2022-11-19T00:00:00", + "2022-11-19T02:00:00", + "2022-11-19T04:00:00", + "2022-11-19T06:00:00", + "2022-11-19T08:00:00", + "2022-11-19T10:00:00", + "2022-11-19T12:00:00", + "2022-11-19T14:00:00", + "2022-11-19T16:00:00", + "2022-11-19T18:00:00", + "2022-11-19T20:00:00", + "2022-11-19T22:00:00", + "2022-11-20T00:00:00", + "2022-11-20T02:00:00", + "2022-11-20T04:00:00", + "2022-11-20T06:00:00", + "2022-11-20T08:00:00", + "2022-11-20T10:00:00", + "2022-11-20T12:00:00", + "2022-11-20T14:00:00", + "2022-11-20T16:00:00", + "2022-11-20T18:00:00", + "2022-11-20T20:00:00", + "2022-11-20T22:00:00", + "2022-11-21T00:00:00", + "2022-11-21T02:00:00", + "2022-11-21T04:00:00", + "2022-11-21T06:00:00", + "2022-11-21T08:00:00", + "2022-11-21T10:00:00", + "2022-11-21T12:00:00", + "2022-11-21T14:00:00", + "2022-11-21T16:00:00", + "2022-11-21T18:00:00", + "2022-11-21T20:00:00", + "2022-11-21T22:00:00", + "2022-11-22T00:00:00", + "2022-11-22T02:00:00", + "2022-11-22T04:00:00", + "2022-11-22T06:00:00", + "2022-11-22T08:00:00", + "2022-11-22T10:00:00", + "2022-11-22T12:00:00", + "2022-11-22T14:00:00", + "2022-11-22T16:00:00", + "2022-11-22T18:00:00", + "2022-11-22T20:00:00", + "2022-11-22T22:00:00", + "2022-11-23T00:00:00", + "2022-11-23T02:00:00", + "2022-11-23T04:00:00", + "2022-11-23T06:00:00", + "2022-11-23T08:00:00", + "2022-11-23T10:00:00", + "2022-11-23T12:00:00", + "2022-11-23T14:00:00", + "2022-11-23T16:00:00", + "2022-11-23T18:00:00", + "2022-11-23T20:00:00", + "2022-11-23T22:00:00", + "2022-11-24T00:00:00", + "2022-11-24T02:00:00", + "2022-11-24T04:00:00", + "2022-11-24T06:00:00", + "2022-11-24T08:00:00", + "2022-11-24T10:00:00", + "2022-11-24T12:00:00", + "2022-11-24T14:00:00", + "2022-11-24T16:00:00", + "2022-11-24T18:00:00", + "2022-11-24T20:00:00", + "2022-11-24T22:00:00", + "2022-11-25T00:00:00", + "2022-11-25T02:00:00", + "2022-11-25T04:00:00", + "2022-11-25T06:00:00", + "2022-11-25T08:00:00", + "2022-11-25T10:00:00", + "2022-11-25T12:00:00", + "2022-11-25T14:00:00", + "2022-11-25T16:00:00", + "2022-11-25T18:00:00", + "2022-11-25T20:00:00", + "2022-11-25T22:00:00", + "2022-11-26T00:00:00", + "2022-11-26T02:00:00", + "2022-11-26T04:00:00", + "2022-11-26T06:00:00", + "2022-11-26T08:00:00", + "2022-11-26T10:00:00", + "2022-11-26T12:00:00", + "2022-11-26T14:00:00", + "2022-11-26T16:00:00", + "2022-11-26T18:00:00", + "2022-11-26T20:00:00", + "2022-11-26T22:00:00", + "2022-11-27T00:00:00", + "2022-11-27T02:00:00", + "2022-11-27T04:00:00", + "2022-11-27T06:00:00", + "2022-11-27T08:00:00", + "2022-11-27T10:00:00", + "2022-11-27T12:00:00", + "2022-11-27T14:00:00", + "2022-11-27T16:00:00", + "2022-11-27T18:00:00", + "2022-11-27T20:00:00", + "2022-11-27T22:00:00", + "2022-11-28T00:00:00", + "2022-11-28T02:00:00", + "2022-11-28T04:00:00", + "2022-11-28T06:00:00", + "2022-11-28T08:00:00", + "2022-11-28T10:00:00", + "2022-11-28T12:00:00", + "2022-11-28T14:00:00", + "2022-11-28T16:00:00", + "2022-11-28T18:00:00", + "2022-11-28T20:00:00", + "2022-11-28T22:00:00", + "2022-11-29T00:00:00", + "2022-11-29T02:00:00", + "2022-11-29T04:00:00", + "2022-11-29T06:00:00", + "2022-11-29T08:00:00", + "2022-11-29T10:00:00", + "2022-11-29T12:00:00", + "2022-11-29T14:00:00", + "2022-11-29T16:00:00", + "2022-11-29T18:00:00", + "2022-11-29T20:00:00", + "2022-11-29T22:00:00", + "2022-11-30T00:00:00", + "2022-11-30T02:00:00", + "2022-11-30T04:00:00", + "2022-11-30T06:00:00", + "2022-11-30T08:00:00", + "2022-11-30T10:00:00", + "2022-11-30T12:00:00", + "2022-11-30T14:00:00", + "2022-11-30T16:00:00", + "2022-11-30T18:00:00", + "2022-11-30T20:00:00", + "2022-11-30T22:00:00", + "2022-12-01T00:00:00", + "2022-12-01T02:00:00", + "2022-12-01T04:00:00", + "2022-12-01T06:00:00", + "2022-12-01T08:00:00", + "2022-12-01T10:00:00", + "2022-12-01T12:00:00", + "2022-12-01T14:00:00", + "2022-12-01T16:00:00", + "2022-12-01T18:00:00", + "2022-12-01T20:00:00", + "2022-12-01T22:00:00", + "2022-12-02T00:00:00", + "2022-12-02T02:00:00", + "2022-12-02T04:00:00", + "2022-12-02T06:00:00", + "2022-12-02T08:00:00", + "2022-12-02T10:00:00", + "2022-12-02T12:00:00", + "2022-12-02T14:00:00", + "2022-12-02T16:00:00", + "2022-12-02T18:00:00", + "2022-12-02T20:00:00", + "2022-12-02T22:00:00", + "2022-12-03T00:00:00", + "2022-12-03T02:00:00", + "2022-12-03T04:00:00", + "2022-12-03T06:00:00", + "2022-12-03T08:00:00", + "2022-12-03T10:00:00", + "2022-12-03T12:00:00", + "2022-12-03T14:00:00", + "2022-12-03T16:00:00", + "2022-12-03T18:00:00", + "2022-12-03T20:00:00", + "2022-12-03T22:00:00", + "2022-12-04T00:00:00", + "2022-12-04T02:00:00", + "2022-12-04T04:00:00", + "2022-12-04T06:00:00", + "2022-12-04T08:00:00", + "2022-12-04T10:00:00", + "2022-12-04T12:00:00", + "2022-12-04T14:00:00", + "2022-12-04T16:00:00", + "2022-12-04T18:00:00", + "2022-12-04T20:00:00", + "2022-12-04T22:00:00", + "2022-12-05T00:00:00", + "2022-12-05T02:00:00", + "2022-12-05T04:00:00", + "2022-12-05T06:00:00", + "2022-12-05T08:00:00", + "2022-12-05T10:00:00", + "2022-12-05T12:00:00", + "2022-12-05T14:00:00", + "2022-12-05T16:00:00", + "2022-12-05T18:00:00", + "2022-12-05T20:00:00", + "2022-12-05T22:00:00", + "2022-12-06T00:00:00", + "2022-12-06T02:00:00", + "2022-12-06T04:00:00", + "2022-12-06T06:00:00", + "2022-12-06T08:00:00", + "2022-12-06T10:00:00", + "2022-12-06T12:00:00", + "2022-12-06T14:00:00", + "2022-12-06T16:00:00", + "2022-12-06T18:00:00", + "2022-12-06T20:00:00", + "2022-12-06T22:00:00", + "2022-12-07T00:00:00", + "2022-12-07T02:00:00", + "2022-12-07T04:00:00", + "2022-12-07T06:00:00", + "2022-12-07T08:00:00", + "2022-12-07T10:00:00", + "2022-12-07T12:00:00", + "2022-12-07T14:00:00", + "2022-12-07T16:00:00", + "2022-12-07T18:00:00", + "2022-12-07T20:00:00", + "2022-12-07T22:00:00", + "2022-12-08T00:00:00", + "2022-12-08T02:00:00", + "2022-12-08T04:00:00", + "2022-12-08T06:00:00", + "2022-12-08T08:00:00", + "2022-12-08T10:00:00", + "2022-12-08T12:00:00", + "2022-12-08T14:00:00", + "2022-12-08T16:00:00", + "2022-12-08T18:00:00", + "2022-12-08T20:00:00", + "2022-12-08T22:00:00", + "2022-12-09T00:00:00", + "2022-12-09T02:00:00", + "2022-12-09T04:00:00", + "2022-12-09T06:00:00", + "2022-12-09T08:00:00", + "2022-12-09T10:00:00", + "2022-12-09T12:00:00", + "2022-12-09T14:00:00", + "2022-12-09T16:00:00", + "2022-12-09T18:00:00", + "2022-12-09T20:00:00", + "2022-12-09T22:00:00", + "2022-12-10T00:00:00", + "2022-12-10T02:00:00", + "2022-12-10T04:00:00", + "2022-12-10T06:00:00", + "2022-12-10T08:00:00", + "2022-12-10T10:00:00", + "2022-12-10T12:00:00", + "2022-12-10T14:00:00", + "2022-12-10T16:00:00", + "2022-12-10T18:00:00", + "2022-12-10T20:00:00", + "2022-12-10T22:00:00", + "2022-12-11T00:00:00", + "2022-12-11T02:00:00", + "2022-12-11T04:00:00", + "2022-12-11T06:00:00", + "2022-12-11T08:00:00", + "2022-12-11T10:00:00", + "2022-12-11T12:00:00", + "2022-12-11T14:00:00", + "2022-12-11T16:00:00", + "2022-12-11T18:00:00", + "2022-12-11T20:00:00", + "2022-12-11T22:00:00", + "2022-12-12T00:00:00", + "2022-12-12T02:00:00", + "2022-12-12T04:00:00", + "2022-12-12T06:00:00", + "2022-12-12T08:00:00", + "2022-12-12T10:00:00", + "2022-12-12T12:00:00", + "2022-12-12T14:00:00", + "2022-12-12T16:00:00", + "2022-12-12T18:00:00", + "2022-12-12T20:00:00", + "2022-12-12T22:00:00", + "2022-12-13T00:00:00", + "2022-12-13T02:00:00", + "2022-12-13T04:00:00", + "2022-12-13T06:00:00", + "2022-12-13T08:00:00", + "2022-12-13T10:00:00", + "2022-12-13T12:00:00", + "2022-12-13T14:00:00", + "2022-12-13T16:00:00", + "2022-12-13T18:00:00", + "2022-12-13T20:00:00", + "2022-12-13T22:00:00", + "2022-12-14T00:00:00", + "2022-12-14T02:00:00", + "2022-12-14T04:00:00", + "2022-12-14T06:00:00", + "2022-12-14T08:00:00", + "2022-12-14T10:00:00", + "2022-12-14T12:00:00", + "2022-12-14T14:00:00", + "2022-12-14T16:00:00", + "2022-12-14T18:00:00", + "2022-12-14T20:00:00", + "2022-12-14T22:00:00", + "2022-12-15T00:00:00", + "2022-12-15T02:00:00", + "2022-12-15T04:00:00", + "2022-12-15T06:00:00", + "2022-12-15T08:00:00", + "2022-12-15T10:00:00", + "2022-12-15T12:00:00", + "2022-12-15T14:00:00", + "2022-12-15T16:00:00", + "2022-12-15T18:00:00", + "2022-12-15T20:00:00", + "2022-12-15T22:00:00", + "2022-12-16T00:00:00", + "2022-12-16T02:00:00", + "2022-12-16T04:00:00", + "2022-12-16T06:00:00", + "2022-12-16T08:00:00", + "2022-12-16T10:00:00", + "2022-12-16T12:00:00", + "2022-12-16T14:00:00", + "2022-12-16T16:00:00", + "2022-12-16T18:00:00", + "2022-12-16T20:00:00", + "2022-12-16T22:00:00", + "2022-12-17T00:00:00", + "2022-12-17T02:00:00", + "2022-12-17T04:00:00", + "2022-12-17T06:00:00", + "2022-12-17T08:00:00", + "2022-12-17T10:00:00", + "2022-12-17T12:00:00", + "2022-12-17T14:00:00", + "2022-12-17T16:00:00", + "2022-12-17T18:00:00", + "2022-12-17T20:00:00", + "2022-12-17T22:00:00", + "2022-12-18T00:00:00", + "2022-12-18T02:00:00", + "2022-12-18T04:00:00", + "2022-12-18T06:00:00", + "2022-12-18T08:00:00", + "2022-12-18T10:00:00", + "2022-12-18T12:00:00", + "2022-12-18T14:00:00", + "2022-12-18T16:00:00", + "2022-12-18T18:00:00", + "2022-12-18T20:00:00", + "2022-12-18T22:00:00", + "2022-12-19T00:00:00", + "2022-12-19T02:00:00", + "2022-12-19T04:00:00", + "2022-12-19T06:00:00", + "2022-12-19T08:00:00", + "2022-12-19T10:00:00", + "2022-12-19T12:00:00", + "2022-12-19T14:00:00", + "2022-12-19T16:00:00", + "2022-12-19T18:00:00", + "2022-12-19T20:00:00", + "2022-12-19T22:00:00", + "2022-12-20T00:00:00", + "2022-12-20T02:00:00", + "2022-12-20T04:00:00", + "2022-12-20T06:00:00", + "2022-12-20T08:00:00", + "2022-12-20T10:00:00", + "2022-12-20T12:00:00", + "2022-12-20T14:00:00", + "2022-12-20T16:00:00", + "2022-12-20T18:00:00", + "2022-12-20T20:00:00", + "2022-12-20T22:00:00", + "2022-12-21T00:00:00", + "2022-12-21T02:00:00", + "2022-12-21T04:00:00", + "2022-12-21T06:00:00", + "2022-12-21T08:00:00", + "2022-12-21T10:00:00", + "2022-12-21T12:00:00", + "2022-12-21T14:00:00", + "2022-12-21T16:00:00", + "2022-12-21T18:00:00", + "2022-12-21T20:00:00", + "2022-12-21T22:00:00", + "2022-12-22T00:00:00", + "2022-12-22T02:00:00", + "2022-12-22T04:00:00", + "2022-12-22T06:00:00", + "2022-12-22T08:00:00", + "2022-12-22T10:00:00", + "2022-12-22T12:00:00", + "2022-12-22T14:00:00", + "2022-12-22T16:00:00", + "2022-12-22T18:00:00", + "2022-12-22T20:00:00", + "2022-12-22T22:00:00", + "2022-12-23T00:00:00", + "2022-12-23T02:00:00", + "2022-12-23T04:00:00", + "2022-12-23T06:00:00", + "2022-12-23T08:00:00", + "2022-12-23T10:00:00", + "2022-12-23T12:00:00", + "2022-12-23T14:00:00", + "2022-12-23T16:00:00", + "2022-12-23T18:00:00", + "2022-12-23T20:00:00", + "2022-12-23T22:00:00", + "2022-12-24T00:00:00", + "2022-12-24T02:00:00", + "2022-12-24T04:00:00", + "2022-12-24T06:00:00", + "2022-12-24T08:00:00", + "2022-12-24T10:00:00", + "2022-12-24T12:00:00", + "2022-12-24T14:00:00", + "2022-12-24T16:00:00", + "2022-12-24T18:00:00", + "2022-12-24T20:00:00", + "2022-12-24T22:00:00", + "2022-12-25T00:00:00", + "2022-12-25T02:00:00", + "2022-12-25T04:00:00", + "2022-12-25T06:00:00", + "2022-12-25T08:00:00", + "2022-12-25T10:00:00", + "2022-12-25T12:00:00", + "2022-12-25T14:00:00", + "2022-12-25T16:00:00", + "2022-12-25T18:00:00", + "2022-12-25T20:00:00", + "2022-12-25T22:00:00", + "2022-12-26T00:00:00", + "2022-12-26T02:00:00", + "2022-12-26T04:00:00", + "2022-12-26T06:00:00", + "2022-12-26T08:00:00", + "2022-12-26T10:00:00", + "2022-12-26T12:00:00", + "2022-12-26T14:00:00", + "2022-12-26T16:00:00", + "2022-12-26T18:00:00", + "2022-12-26T20:00:00", + "2022-12-26T22:00:00", + "2022-12-27T00:00:00", + "2022-12-27T02:00:00", + "2022-12-27T04:00:00", + "2022-12-27T06:00:00", + "2022-12-27T08:00:00", + "2022-12-27T10:00:00", + "2022-12-27T12:00:00", + "2022-12-27T14:00:00", + "2022-12-27T16:00:00", + "2022-12-27T18:00:00", + "2022-12-27T20:00:00", + "2022-12-27T22:00:00", + "2022-12-28T00:00:00", + "2022-12-28T02:00:00", + "2022-12-28T04:00:00", + "2022-12-28T06:00:00", + "2022-12-28T08:00:00", + "2022-12-28T10:00:00", + "2022-12-28T12:00:00", + "2022-12-28T14:00:00", + "2022-12-28T16:00:00", + "2022-12-28T18:00:00", + "2022-12-28T20:00:00", + "2022-12-28T22:00:00", + "2022-12-29T00:00:00", + "2022-12-29T02:00:00", + "2022-12-29T04:00:00", + "2022-12-29T06:00:00", + "2022-12-29T08:00:00", + "2022-12-29T10:00:00", + "2022-12-29T12:00:00", + "2022-12-29T14:00:00", + "2022-12-29T16:00:00", + "2022-12-29T18:00:00", + "2022-12-29T20:00:00", + "2022-12-29T22:00:00", + "2022-12-30T00:00:00", + "2022-12-30T02:00:00", + "2022-12-30T04:00:00", + "2022-12-30T06:00:00", + "2022-12-30T08:00:00", + "2022-12-30T10:00:00", + "2022-12-30T12:00:00", + "2022-12-30T14:00:00", + "2022-12-30T16:00:00", + "2022-12-30T18:00:00", + "2022-12-30T20:00:00", + "2022-12-30T22:00:00", + "2022-12-31T00:00:00", + "2022-12-31T02:00:00", + "2022-12-31T04:00:00", + "2022-12-31T06:00:00", + "2022-12-31T08:00:00", + "2022-12-31T10:00:00", + "2022-12-31T12:00:00", + "2022-12-31T14:00:00", + "2022-12-31T16:00:00", + "2022-12-31T18:00:00", + "2022-12-31T20:00:00", + "2022-12-31T22:00:00", + "2023-01-01T00:00:00", + "2023-01-01T02:00:00", + "2023-01-01T04:00:00", + "2023-01-01T06:00:00", + "2023-01-01T08:00:00", + "2023-01-01T10:00:00", + "2023-01-01T12:00:00", + "2023-01-01T14:00:00", + "2023-01-01T16:00:00", + "2023-01-01T18:00:00", + "2023-01-01T20:00:00", + "2023-01-01T22:00:00", + "2023-01-02T00:00:00", + "2023-01-02T02:00:00", + "2023-01-02T04:00:00", + "2023-01-02T06:00:00", + "2023-01-02T08:00:00", + "2023-01-02T10:00:00", + "2023-01-02T12:00:00", + "2023-01-02T14:00:00", + "2023-01-02T16:00:00", + "2023-01-02T18:00:00", + "2023-01-02T20:00:00", + "2023-01-02T22:00:00", + "2023-01-03T00:00:00", + "2023-01-03T02:00:00", + "2023-01-03T04:00:00", + "2023-01-03T06:00:00", + "2023-01-03T08:00:00", + "2023-01-03T10:00:00", + "2023-01-03T12:00:00", + "2023-01-03T14:00:00", + "2023-01-03T16:00:00", + "2023-01-03T18:00:00", + "2023-01-03T20:00:00", + "2023-01-03T22:00:00", + "2023-01-04T00:00:00", + "2023-01-04T02:00:00", + "2023-01-04T04:00:00", + "2023-01-04T06:00:00", + "2023-01-04T08:00:00", + "2023-01-04T10:00:00", + "2023-01-04T12:00:00", + "2023-01-04T14:00:00", + "2023-01-04T16:00:00", + "2023-01-04T18:00:00", + "2023-01-04T20:00:00", + "2023-01-04T22:00:00", + "2023-01-05T00:00:00", + "2023-01-05T02:00:00", + "2023-01-05T04:00:00", + "2023-01-05T06:00:00", + "2023-01-05T08:00:00", + "2023-01-05T10:00:00", + "2023-01-05T12:00:00", + "2023-01-05T14:00:00", + "2023-01-05T16:00:00", + "2023-01-05T18:00:00", + "2023-01-05T20:00:00", + "2023-01-05T22:00:00", + "2023-01-06T00:00:00", + "2023-01-06T02:00:00", + "2023-01-06T04:00:00", + "2023-01-06T06:00:00", + "2023-01-06T08:00:00", + "2023-01-06T10:00:00", + "2023-01-06T12:00:00", + "2023-01-06T14:00:00", + "2023-01-06T16:00:00", + "2023-01-06T18:00:00", + "2023-01-06T20:00:00", + "2023-01-06T22:00:00", + "2023-01-07T00:00:00", + "2023-01-07T02:00:00", + "2023-01-07T04:00:00", + "2023-01-07T06:00:00", + "2023-01-07T08:00:00", + "2023-01-07T10:00:00", + "2023-01-07T12:00:00", + "2023-01-07T14:00:00", + "2023-01-07T16:00:00", + "2023-01-07T18:00:00", + "2023-01-07T20:00:00", + "2023-01-07T22:00:00", + "2023-01-08T00:00:00", + "2023-01-08T02:00:00", + "2023-01-08T04:00:00", + "2023-01-08T06:00:00", + "2023-01-08T08:00:00", + "2023-01-08T10:00:00", + "2023-01-08T12:00:00", + "2023-01-08T14:00:00", + "2023-01-08T16:00:00", + "2023-01-08T18:00:00", + "2023-01-08T20:00:00", + "2023-01-08T22:00:00", + "2023-01-09T00:00:00", + "2023-01-09T02:00:00", + "2023-01-09T04:00:00", + "2023-01-09T06:00:00", + "2023-01-09T08:00:00", + "2023-01-09T10:00:00", + "2023-01-09T12:00:00", + "2023-01-09T14:00:00", + "2023-01-09T16:00:00", + "2023-01-09T18:00:00", + "2023-01-09T20:00:00", + "2023-01-09T22:00:00", + "2023-01-10T00:00:00" + ], + "xaxis": "x3", + "y": [ + 28304, + 28309, + 28268, + 28190, + 28370, + 27909, + 27989, + 27664, + 27527, + 27745, + 27666, + 27751, + 27816, + 27854, + 27895, + 27642, + 27334, + 27381, + 27026, + 27114, + 27209, + 27141, + 27012, + 26810, + 26235, + 25957, + 26317, + 26155, + 26117, + 25984, + 26725, + 26750, + 26040, + 26007, + 25305, + 24576, + 24286, + 24034, + 23681, + 22947, + 22676, + 22646, + 22310, + 22526, + 22270, + 22162, + 21584, + 20091, + 21217, + 21811, + 21855, + 21361, + 21037, + 21473, + 21784, + 21455, + 21325, + 20624, + 21232, + 21211, + 20512, + 20264, + 19795, + 19254, + 20227, + 20638, + 20581, + 20275, + 20686, + 21241, + 21582, + 21426, + 21230, + 21033, + 20938, + 20410, + 20261, + 20111, + 19988, + 19890, + 19765, + 19552, + 19331, + 19796, + 19339, + 19760, + 20052, + 19910, + 19987, + 19811, + 19733, + 19589, + 19525, + 19566, + 19466, + 19442, + 19480, + 19445, + 18364, + 18275, + 18336, + 18292, + 18039, + 17710, + 17188, + 17614, + 18068, + 17558, + 17419, + 17264, + 17431, + 18098, + 18714, + 18280, + 18856, + 18523, + 19162, + 19450, + 19608, + 18851, + 18953, + 19047, + 19075, + 19577, + 19745, + 19489, + 19753, + 18979, + 19097, + 19398, + 19531, + 19583, + 19538, + 19837, + 19995, + 20042, + 19834, + 20475, + 20267, + 20131, + 19827, + 19916, + 19671, + 19415, + 19295, + 19399, + 19126, + 19416, + 19479, + 19637, + 18963, + 19158, + 19042, + 18921, + 18884, + 19288, + 19120, + 19248, + 19481, + 19737, + 19635, + 19389, + 19364, + 19331, + 19853, + 19639, + 20052, + 19962, + 19996, + 19816, + 19870, + 19727, + 20102, + 20109, + 19810, + 19879, + 20118, + 20184, + 20090, + 20182, + 20190, + 20100, + 20386, + 20332, + 20232, + 20063, + 19879, + 20005, + 20091, + 20365, + 20354, + 20279, + 20294, + 20322, + 20278, + 20312, + 20572, + 20305, + 20232, + 20151, + 20181, + 20243, + 19923, + 19962, + 20069, + 20050, + 20149, + 20288, + 20143, + 19599, + 19575, + 19571, + 19709, + 19623, + 19585, + 19476, + 19586, + 19611, + 19729, + 19882, + 19841, + 19980, + 19530, + 19569, + 19236, + 19367, + 19263, + 19324, + 19257, + 19164, + 19082, + 19070, + 19066, + 19143, + 19147, + 19173, + 19334, + 19406, + 19241, + 19158, + 19174, + 19115, + 18594, + 18357, + 18363, + 18107, + 18280, + 18230, + 18039, + 17949, + 19020, + 19466, + 18837, + 18562, + 18725, + 18555, + 18377, + 18722, + 18509, + 18708, + 18565, + 18564, + 18470, + 18435, + 18345, + 18444, + 18339, + 18388, + 18393, + 18466, + 18440, + 18488, + 18478, + 18535, + 18431, + 18396, + 18462, + 18247, + 18341, + 18257, + 18307, + 18191, + 18266, + 18405, + 18693, + 18508, + 18496, + 18380, + 18317, + 18333, + 18314, + 18530, + 18922, + 18668, + 18956, + 19054, + 19004, + 19107, + 19350, + 19407, + 19444, + 19391, + 19445, + 19179, + 18919, + 18888, + 19011, + 19192, + 19825, + 19807, + 19633, + 19342, + 19419, + 19534, + 19630, + 19733, + 19696, + 19857, + 19787, + 19893, + 19923, + 19966, + 20171, + 20129, + 20058, + 19831, + 20045, + 20118, + 20101, + 20133, + 20527, + 20549, + 21473, + 21313, + 21260, + 21709, + 21786, + 21494, + 21548, + 21289, + 21241, + 20951, + 21552, + 21370, + 21400, + 21320, + 21197, + 21120, + 21223, + 21143, + 21247, + 21267, + 21065, + 21189, + 21170, + 21491, + 21238, + 21326, + 21200, + 21014, + 20902, + 20984, + 20963, + 20929, + 20904, + 20690, + 20520, + 20524, + 20545, + 20660, + 20499, + 20252, + 20237, + 20209, + 20200, + 20329, + 20268, + 20230, + 20254, + 20458, + 20381, + 20135, + 19867, + 19808, + 19884, + 19916, + 19702, + 19559, + 19691, + 19659, + 19742, + 19759, + 19340, + 19274, + 19239, + 19365, + 19397, + 19444, + 19640, + 19722, + 19713, + 19222, + 19294, + 19721, + 19532, + 19810, + 20171, + 20215, + 20125, + 20044, + 19905, + 19732, + 19719, + 19815, + 20304, + 20682, + 20550, + 20402, + 20506, + 20533, + 20528, + 20600, + 20749, + 20809, + 20635, + 20773, + 20611, + 20616, + 20979, + 20790, + 20633, + 20539, + 20557, + 20399, + 20383, + 20444, + 20443, + 20688, + 20780, + 21038, + 20982, + 21012, + 21040, + 20948, + 21146, + 21157, + 21282, + 21181, + 21268, + 20953, + 21000, + 20731, + 20856, + 20836, + 20581, + 20745, + 21076, + 21647, + 21912, + 21893, + 21812, + 21780, + 22257, + 21531, + 21301, + 21355, + 22145, + 21788, + 21623, + 21688, + 21249, + 21420, + 21452, + 21789, + 22613, + 22942, + 22924, + 23113, + 22889, + 22731, + 22881, + 22947, + 22864, + 22969, + 23287, + 23280, + 23689, + 23152, + 23261, + 23211, + 22828, + 22786, + 22310, + 22454, + 22477, + 22602, + 22168, + 22252, + 22200, + 22423, + 22713, + 22700, + 22632, + 22661, + 22494, + 22728, + 22807, + 23101, + 23204, + 22921, + 22793, + 22504, + 22136, + 22272, + 22213, + 22313, + 22381, + 22424, + 22304, + 22229, + 21785, + 21912, + 21766, + 21604, + 21844, + 21818, + 21997, + 21945, + 21881, + 22299, + 22250, + 22172, + 22209, + 22101, + 22372, + 22319, + 22238, + 22357, + 22141, + 21797, + 21431, + 21514, + 21569, + 21492, + 21427, + 21400, + 21466, + 21418, + 21424, + 21565, + 20836, + 20588, + 20683, + 20716, + 20624, + 20759, + 20805, + 20669, + 20500, + 20694, + 20692, + 20702, + 21000, + 20869, + 20784, + 20941, + 21070, + 20981, + 20872, + 21178, + 21314, + 21354, + 22290, + 22260, + 22501, + 22319, + 22637, + 22752, + 22443, + 22634, + 22837, + 22528, + 23341, + 23638, + 23349, + 23448, + 23412, + 23418, + 23424, + 23513, + 23401, + 23609, + 23228, + 23332, + 23539, + 23299, + 23375, + 23287, + 23267, + 23384, + 23299, + 23269, + 23364, + 23411, + 23460, + 23956, + 23985, + 23963, + 23421, + 23372, + 23133, + 23227, + 23262, + 23216, + 23233, + 23288, + 23300, + 23250, + 23270, + 23245, + 23347, + 23101, + 22862, + 22908, + 22866, + 22854, + 22814, + 22724, + 22717, + 22672, + 22748, + 22399, + 22395, + 22455, + 22683, + 22408, + 22243, + 22282, + 22418, + 22180, + 22388, + 22353, + 22550, + 22782, + 22613, + 22710, + 22645, + 22440, + 22444, + 22535, + 22591, + 22969, + 23022, + 22951, + 23026, + 23103, + 23066, + 22913, + 22508, + 22768, + 22724, + 22769, + 22579, + 22441, + 22515, + 22630, + 22440, + 22125, + 21937, + 21951, + 22091, + 22390, + 22628, + 22662, + 22665, + 22614, + 22863, + 22670, + 22625, + 22439, + 22526, + 22686, + 22878, + 22787, + 22740, + 22782, + 22797, + 22770, + 22756, + 22792, + 22785, + 22721, + 22796, + 22781, + 22540, + 22508, + 22616, + 22562, + 22623, + 22590, + 22642, + 22768, + 22618, + 22832, + 22759, + 22924, + 22807, + 22818, + 22891, + 23017, + 23335, + 23643, + 23674, + 23700, + 23515, + 23485, + 23496, + 23410, + 23331, + 23303, + 23390, + 23416, + 23393, + 22951, + 22791, + 22555, + 22617, + 22593, + 22617, + 22733, + 22685, + 22432, + 22395, + 22428, + 22490, + 22586, + 22556, + 23265, + 23214, + 23162, + 22928, + 23260, + 23229, + 23728, + 23647, + 23874, + 23656, + 23668, + 23804, + 23868, + 23564, + 23578, + 23442, + 23447, + 23215, + 23229, + 23307, + 23232, + 23282, + 23314, + 23052, + 23197, + 23403, + 23417, + 23588, + 23526, + 23790, + 23926, + 24149, + 24063, + 23915, + 23970, + 23805, + 23856, + 23899, + 23964, + 23812, + 23912, + 23840, + 23966, + 23967, + 23992, + 24256, + 24157, + 23956, + 23901, + 23944, + 23650, + 23692, + 23755, + 23709, + 24022, + 24253, + 24254, + 23500, + 23727, + 23617, + 23718, + 23802, + 23746, + 23604, + 23648, + 23732, + 23768, + 23722, + 23569, + 23686, + 23779, + 23729, + 23507, + 23491, + 23463, + 23518, + 23570, + 23466, + 23527, + 23584, + 23752, + 23406, + 23412, + 23347, + 23052, + 23035, + 23098, + 22856, + 22938, + 22940, + 23055, + 23041, + 23012, + 23042, + 23144, + 23118, + 23165, + 23199, + 23091, + 23155, + 23162, + 22981, + 22600, + 22610, + 22633, + 21729, + 21570, + 21371, + 21272, + 21375, + 21385, + 21264, + 21000, + 20729, + 20899, + 21054, + 21057, + 21249, + 21140, + 21192, + 21246, + 21245, + 21190, + 20890, + 21046, + 21058, + 21175, + 21118, + 21143, + 21161, + 21383, + 21460, + 21326, + 21349, + 21401, + 21432, + 21445, + 21451, + 21394, + 21331, + 21369, + 21098, + 21150, + 21253, + 21362, + 21548, + 21392, + 21177, + 21216, + 21521, + 21430, + 21360, + 21138, + 21454, + 21589, + 21629, + 21555, + 21533, + 21515, + 21600, + 21539, + 21581, + 21364, + 21371, + 21534, + 21420, + 21425, + 21607, + 21610, + 21687, + 21714, + 21771, + 21657, + 21429, + 21614, + 21504, + 21584, + 21743, + 21700, + 21775, + 21791, + 21668, + 21635, + 21661, + 21611, + 21639, + 21609, + 21628, + 21500, + 21500, + 21410, + 21191, + 21624, + 20791, + 20704, + 20727, + 20733, + 20342, + 20383, + 20321, + 20238, + 20384, + 20254, + 20315, + 20276, + 20063, + 20083, + 20117, + 19975, + 20108, + 20105, + 20086, + 20110, + 20140, + 20104, + 20133, + 20039, + 20124, + 20066, + 20069, + 20042, + 19685, + 19809, + 20028, + 19908, + 20013, + 19905, + 19821, + 20055, + 20274, + 20282, + 20090, + 20221, + 20268, + 20194, + 20272, + 20419, + 20379, + 20346, + 20368, + 20226, + 19744, + 19669, + 19907, + 19852, + 19754, + 20187, + 20334, + 20254, + 20244, + 20198, + 20308, + 20303, + 20039, + 19898, + 20124, + 20167, + 19986, + 20064, + 19972, + 20023, + 19828, + 19883, + 20080, + 19973, + 19890, + 19804, + 19959, + 20178, + 20197, + 20095, + 20223, + 20196, + 20117, + 20112, + 20114, + 20254, + 20286, + 19872, + 20010, + 20096, + 20068, + 20061, + 20001, + 20057, + 19896, + 19906, + 19889, + 19960, + 19888, + 19931, + 19803, + 19867, + 19943, + 19896, + 19876, + 19885, + 19770, + 19893, + 19929, + 19818, + 19903, + 19925, + 19988, + 20001, + 20170, + 19994, + 20049, + 19952, + 19934, + 19930, + 19873, + 19884, + 19975, + 19946, + 19835, + 19896, + 19875, + 19819, + 19862, + 19887, + 19966, + 20001, + 20060, + 19973, + 20008, + 19439, + 19023, + 19175, + 19002, + 19002, + 18982, + 18976, + 18968, + 18966, + 18990, + 19043, + 19007, + 19030, + 19000, + 19340, + 19293, + 19210, + 19224, + 19314, + 19206, + 19190, + 19264, + 19287, + 19356, + 19203, + 19365, + 19347, + 19284, + 19250, + 19798, + 20365, + 20426, + 20562, + 20845, + 20898, + 21154, + 21146, + 21164, + 21126, + 21234, + 21157, + 21161, + 21418, + 21177, + 21195, + 21155, + 21050, + 21088, + 21179, + 21450, + 21479, + 21530, + 21486, + 21416, + 21494, + 21500, + 21470, + 21486, + 21411, + 21505, + 21521, + 21471, + 21284, + 21680, + 21825, + 21460, + 21612, + 21799, + 21834, + 21995, + 22134, + 21825, + 22046, + 22161, + 22058, + 22109, + 21947, + 21956, + 22078, + 21955, + 22011, + 22145, + 21090, + 20793, + 20809, + 20358, + 20235, + 20246, + 20356, + 20358, + 20351, + 20269, + 20334, + 20245, + 20343, + 20201, + 20139, + 20014, + 20281, + 20248, + 20244, + 20071, + 20235, + 20245, + 20156, + 20186, + 20125, + 19746, + 19828, + 19809, + 19842, + 19746, + 19734, + 19760, + 19789, + 19824, + 19817, + 19936, + 19581, + 19651, + 19453, + 19610, + 19720, + 19776, + 19975, + 19865, + 19821, + 19809, + 19859, + 19793, + 19873, + 19941, + 20072, + 20004, + 20015, + 20099, + 19968, + 19954, + 20038, + 20037, + 19815, + 19898, + 19931, + 19782, + 19600, + 19696, + 19459, + 19384, + 19434, + 18773, + 18524, + 18501, + 18525, + 18716, + 19149, + 19101, + 18963, + 19476, + 19460, + 19474, + 19429, + 19315, + 19331, + 19293, + 19199, + 19246, + 18942, + 19274, + 18851, + 19033, + 18948, + 18940, + 18960, + 19089, + 19029, + 19110, + 19123, + 19345, + 19464, + 19418, + 19851, + 19279, + 18775, + 18845, + 18861, + 19035, + 19010, + 19226, + 19446, + 19479, + 19222, + 19308, + 19364, + 19609, + 19496, + 19719, + 19657, + 19714, + 19720, + 19650, + 19553, + 19325, + 19142, + 19267, + 19369, + 19407, + 19593, + 19885, + 19702, + 19726, + 19738, + 19595, + 19622, + 19650, + 19708, + 19715, + 19682, + 19707, + 19549, + 19521, + 19569, + 19650, + 19590, + 19699, + 19641, + 19708, + 19522, + 19572, + 19579, + 19520, + 19383, + 19407, + 19539, + 19591, + 19520, + 19541, + 19769, + 19570, + 19838, + 19863, + 19892, + 19972, + 19868, + 19972, + 20520, + 20847, + 20916, + 20899, + 20980, + 20986, + 20968, + 20630, + 19784, + 19885, + 19782, + 19887, + 19880, + 19611, + 19680, + 19605, + 19535, + 19785, + 19909, + 20147, + 19886, + 20065, + 20145, + 19987, + 20111, + 20133, + 20118, + 20043, + 20104, + 20005, + 19498, + 19756, + 19700, + 19807, + 19797, + 19942, + 19879, + 19791, + 19801, + 19906, + 19927, + 19925, + 19914, + 20191, + 20104, + 19900, + 19760, + 19818, + 19819, + 19805, + 19704, + 19712, + 19725, + 19707, + 19737, + 19737, + 19681, + 19667, + 19694, + 19713, + 19704, + 19713, + 19730, + 19691, + 19680, + 19615, + 19570, + 19546, + 19589, + 19683, + 19593, + 19448, + 19580, + 19547, + 19617, + 19561, + 19635, + 19700, + 19639, + 19713, + 19950, + 19892, + 19913, + 19970, + 19902, + 19912, + 19938, + 20224, + 20183, + 20159, + 20245, + 20137, + 20095, + 20262, + 20318, + 20387, + 20294, + 20254, + 20268, + 20366, + 20242, + 20226, + 20177, + 20277, + 20417, + 20352, + 20296, + 20350, + 20546, + 20526, + 20463, + 20413, + 20316, + 20511, + 20458, + 20407, + 20483, + 20441, + 20335, + 20405, + 20424, + 20392, + 20392, + 20331, + 20389, + 20452, + 20110, + 20013, + 19900, + 19997, + 20076, + 20071, + 20136, + 20040, + 20027, + 20033, + 20060, + 20016, + 20012, + 20034, + 19997, + 19855, + 19951, + 19918, + 19907, + 19949, + 19949, + 19942, + 20002, + 20001, + 20067, + 20004, + 20000, + 20003, + 19968, + 20014, + 19993, + 19992, + 20010, + 19909, + 19936, + 19919, + 19878, + 19912, + 19817, + 19846, + 19716, + 19621, + 19666, + 19641, + 19660, + 19660, + 19706, + 19582, + 19623, + 19626, + 19581, + 19587, + 19640, + 19658, + 19654, + 19679, + 19705, + 19764, + 19685, + 19714, + 19736, + 19685, + 19745, + 19762, + 19722, + 19702, + 19665, + 19684, + 19598, + 19541, + 19267, + 19031, + 19488, + 19578, + 19811, + 19844, + 19847, + 20258, + 20243, + 20242, + 20090, + 20183, + 20160, + 20205, + 19870, + 19913, + 19710, + 19658, + 19731, + 19735, + 19756, + 19702, + 19728, + 19668, + 19726, + 19721, + 19683, + 19660, + 19661, + 19630, + 19625, + 19709, + 19674, + 19698, + 19699, + 19705, + 19697, + 19686, + 19690, + 19684, + 19907, + 19692, + 19780, + 19682, + 19699, + 19759, + 19801, + 19820, + 19973, + 20025, + 19852, + 19840, + 19837, + 19828, + 19846, + 19855, + 19859, + 19846, + 19955, + 19841, + 19886, + 19827, + 19679, + 19692, + 19499, + 19562, + 19602, + 19579, + 19590, + 19572, + 19530, + 19621, + 19616, + 19541, + 19696, + 19636, + 19670, + 19628, + 19582, + 19517, + 19510, + 19569, + 19550, + 19605, + 19570, + 19594, + 19592, + 19503, + 19492, + 19480, + 19482, + 19506, + 19496, + 19481, + 19432, + 19514, + 19431, + 19465, + 19464, + 19486, + 19461, + 19427, + 19442, + 19434, + 19436, + 19417, + 19432, + 19431, + 19460, + 19474, + 19519, + 19447, + 19468, + 19490, + 19478, + 19443, + 19479, + 19460, + 19434, + 19469, + 19439, + 19447, + 19473, + 19735, + 19766, + 19774, + 19840, + 19745, + 19696, + 19635, + 19641, + 19718, + 19749, + 19600, + 19565, + 19531, + 19576, + 19601, + 19559, + 19511, + 19566, + 19584, + 19581, + 19537, + 19564, + 19552, + 19823, + 20185, + 20305, + 20290, + 20161, + 20360, + 20354, + 20265, + 20279, + 20534, + 20557, + 20514, + 20697, + 20660, + 20594, + 20567, + 20571, + 20583, + 20616, + 20633, + 20574, + 20529, + 20510, + 20523, + 20602, + 20635, + 20670, + 20481, + 20349, + 20326, + 20311, + 20330, + 20177, + 20281, + 20264, + 20539, + 20619, + 20758, + 20676, + 20655, + 20672, + 20694, + 20819, + 20730, + 20836, + 21015, + 20754, + 20850, + 20981, + 20886, + 20902, + 20920, + 20869, + 20849, + 20840, + 20873, + 20926, + 20774, + 20849, + 20728, + 20738, + 20734, + 20715, + 20785, + 20723, + 20625, + 20599, + 20651, + 20694, + 20813, + 20846, + 20579, + 20641, + 20620, + 20601, + 20633, + 20724, + 20727, + 20693, + 20692, + 20726, + 20731, + 20645, + 20562, + 20714, + 20677, + 20706, + 20692, + 20741, + 20744, + 20746, + 20714, + 20657, + 20669, + 20652, + 20604, + 20660, + 20687, + 20623, + 20525, + 20542, + 20667, + 20669, + 20745, + 20760, + 20830, + 20662, + 20625, + 20735, + 20763, + 20774, + 20775, + 20716, + 20770, + 20803, + 20910, + 21091, + 21044, + 20984, + 21034, + 21079, + 20978, + 21185, + 21265, + 21251, + 21447, + 21518, + 21476, + 21466, + 21366, + 21482, + 21371, + 21393, + 21407, + 21419, + 21419, + 21381, + 21304, + 21332, + 21241, + 21281, + 21332, + 21344, + 21330, + 21340, + 21342, + 21350, + 21313, + 21069, + 21128, + 21019, + 20990, + 20792, + 20735, + 20788, + 20760, + 20780, + 20676, + 20788, + 20653, + 20555, + 20615, + 20142, + 19743, + 19837, + 19789, + 19731, + 19574, + 19404, + 19201, + 18235, + 18745, + 18445, + 18226, + 18215, + 18281, + 18168, + 17688, + 17758, + 17603, + 17099, + 16884, + 16814, + 15744, + 15890, + 16121, + 16321, + 16676, + 16753, + 16725, + 16342, + 17346, + 17234, + 16987, + 17143, + 17462, + 17255, + 16955, + 16719, + 16961, + 16968, + 16909, + 16843, + 16760, + 16307, + 16339, + 16216, + 16198, + 16428, + 16289, + 16309, + 16112, + 16212, + 16273, + 16294, + 16250, + 16300, + 16282, + 16269, + 16202, + 16196, + 16277, + 16300, + 16255, + 16165, + 15940, + 16038, + 16039, + 15932, + 15929, + 15939, + 15808, + 15797, + 15576, + 15613, + 15356, + 16282, + 16206, + 16285, + 16225, + 16052, + 16027, + 15691, + 15886, + 16063, + 16173, + 16279, + 16108, + 16188, + 16116, + 16094, + 16293, + 16382, + 16360, + 16255, + 16325, + 16314, + 16300, + 16382, + 16318, + 16170, + 16049, + 16039, + 15858, + 15785, + 15964, + 15931, + 15924, + 16010, + 16092, + 15950, + 15972, + 15990, + 15947, + 16028, + 15987, + 16018, + 16129, + 16022, + 16092, + 16105, + 16243, + 16239, + 16180, + 16130, + 16109, + 16134, + 16164, + 16101, + 16041, + 16098, + 16129, + 16160, + 16113, + 16081, + 16056, + 16069, + 16087, + 16123, + 16116, + 16095, + 16059, + 16085, + 16138, + 16169, + 16150, + 16150, + 16163, + 16194, + 16108, + 16000, + 16036, + 16034, + 16004, + 16035, + 15756, + 15737, + 15704, + 15585, + 15764, + 15604, + 15708, + 15667, + 15756, + 15725, + 15633, + 15353, + 15272, + 15372, + 15378, + 15431, + 15392, + 15307, + 15251, + 15310, + 15717, + 15764, + 15585, + 15696, + 15658, + 15716, + 15719, + 16027, + 15897, + 16001, + 16016, + 16035, + 15842, + 15819, + 15813, + 15841, + 15854, + 15938, + 16046, + 15987, + 15943, + 15951, + 15900, + 15904, + 15910, + 15900, + 15912, + 15911, + 15885, + 15938, + 15853, + 15829, + 15712, + 15798, + 15822, + 15873, + 15884, + 15841, + 15845, + 15861, + 15851, + 15857, + 15976, + 15964, + 15989, + 15942, + 15951, + 15938, + 15970, + 15882, + 15891, + 15858, + 15869, + 15832, + 15847, + 15898, + 15886, + 15925, + 15893, + 15906, + 15908, + 15917, + 15877, + 15920, + 15967, + 15840, + 15591, + 15631, + 15613, + 15652, + 15528, + 15467, + 15473, + 15519, + 15635, + 15701, + 15675, + 15667, + 15692, + 15692, + 15842, + 15853, + 15917, + 15877, + 15870, + 15809, + 15837, + 15913, + 15925, + 15927, + 16290, + 16251, + 16273, + 16314, + 16291, + 16294, + 16224, + 16354, + 16258, + 16400, + 16427, + 16481, + 16418, + 16436, + 16390, + 16436, + 16404, + 16401, + 16292, + 16194, + 16161, + 16129, + 16106, + 16138, + 16096, + 16055, + 16082, + 16123, + 16130, + 16143, + 16166, + 16195, + 16114, + 16147, + 16165, + 16238, + 16184, + 16179, + 16172, + 16122, + 16132, + 16102, + 16111, + 16126, + 16119, + 16117, + 16095, + 16040, + 16133, + 16120, + 16174, + 16153, + 16166, + 16103, + 16108, + 16170, + 16196, + 16209, + 16249, + 16221, + 16339, + 16252, + 16390, + 16441, + 16401, + 16385, + 16306, + 16240, + 16242, + 16136, + 16187, + 16169, + 16200, + 16222, + 16177, + 16208, + 16185, + 16157, + 16170, + 16165, + 16182, + 16224, + 16223, + 16320, + 16268, + 16261, + 16253, + 16022, + 16081, + 16010, + 16002, + 16036, + 15983, + 16003, + 16031, + 16022, + 16026, + 16028, + 16014, + 16000, + 16002, + 16040, + 16008, + 16047, + 16076, + 16327, + 16263, + 16312, + 16285, + 16258, + 16274, + 16277, + 16321, + 16337, + 16280, + 16285, + 16276, + 16239, + 16229, + 16235, + 16276, + 16267, + 16264, + 16277, + 16265, + 16302, + 16273, + 16320, + 16276, + 16301, + 16267, + 16263, + 16299, + 16280, + 16302, + 16295, + 16306, + 16303, + 16263, + 16292, + 16308, + 16311, + 16247, + 16252, + 16121, + 16084, + 16113, + 16065, + 16070, + 16065, + 16057, + 16172, + 16149, + 16190, + 16288, + 16298, + 16237, + 16279, + 16268, + 16271, + 16483, + 16542, + 16794, + 16677, + 16600, + 16678, + 16707, + 16728, + 16718, + 16739, + 16731, + 16750, + 16706, + 16740, + 16791, + 16953, + 16992, + 16687, + 16683, + 16659, + 16538, + 16647, + 16614, + 16632, + 16671, + 16680, + 16445, + 16363, + 16414, + 16390, + 16353, + 16311, + 16309, + 16353, + 16340, + 16452, + 16044, + 16007, + 15914, + 15968, + 15821, + 15900, + 15874, + 15696, + 15763, + 15764, + 15753, + 15832, + 15759, + 15787, + 15754, + 15778, + 15751, + 15774, + 15788, + 15857, + 15781, + 15820, + 15822, + 15816, + 15779, + 15791, + 15792, + 15760, + 15781, + 15814, + 15809, + 15818, + 15843, + 15743, + 15743, + 15748, + 15757, + 15773, + 15741, + 15734, + 15596, + 15593, + 15627, + 15468, + 15603, + 15748, + 15831, + 15832, + 15801, + 15795, + 15801, + 15906, + 15839, + 15898, + 15877, + 15906, + 15868, + 15858, + 15839, + 15818, + 15901, + 15916, + 15846, + 15870, + 15867, + 15774, + 15837, + 15860, + 15820, + 15832, + 15811, + 15805, + 15828, + 15857, + 15793, + 15692, + 15678, + 15734, + 15843, + 15861, + 15825, + 15859, + 15857, + 15850, + 15866, + 15873, + 15883, + 15863, + 15865, + 15869, + 15839, + 15773, + 15832, + 15847, + 15814, + 15854, + 15854, + 15818, + 15809, + 15845, + 15848, + 15847, + 15834, + 15822, + 15826, + 15823, + 15843, + 15834, + 15854, + 15820, + 15830, + 15819, + 15815, + 15805, + 15842, + 15831, + 15879, + 15912, + 15848, + 15827, + 15837, + 15866, + 15842, + 15811, + 15837, + 15836, + 15815, + 15897, + 15820, + 15824, + 15802, + 15821, + 15807, + 15786, + 15823, + 15755, + 15675, + 15678, + 15700, + 15707, + 15673, + 15658, + 15592, + 15637, + 15631, + 15650, + 15619, + 15585, + 15647, + 15622, + 15550, + 15569, + 15544, + 15560, + 15606, + 15579, + 15578, + 15599, + 15578, + 15561, + 15532, + 15562, + 15565, + 15580, + 15585, + 15581, + 15529, + 15473, + 15501, + 15452, + 15438, + 15523, + 15415, + 15454, + 15497, + 15518, + 15479, + 15451, + 15452, + 15476, + 15445, + 15461, + 15510, + 15504, + 15489, + 15470, + 15444, + 15439, + 15437, + 15433, + 15425, + 15426, + 15439, + 15465, + 15462, + 15442, + 15466, + 15504, + 15516, + 15533, + 15483, + 15569, + 15549, + 15661, + 15653, + 15660, + 15629, + 15692, + 15666, + 15682, + 15682, + 15605, + 15639, + 15621, + 15637, + 15710, + 15859, + 15874, + 15833, + 15790, + 15763, + 15729, + 15770, + 15784, + 15803, + 15929, + 15921, + 15897, + 15869, + 15869, + 15856, + 15889, + 15936, + 15872, + 15849, + 15896, + 15838, + 15866, + 15863, + 15847, + 15797, + 15848, + 15895, + 16001, + 15970, + 16001, + 15992, + 16001, + 15975, + 15991, + 15988, + 15966, + 15953, + 15944, + 15887, + 15871, + 15823, + 15888, + 15891, + 15924, + 15926, + 15922, + 15910, + 15929, + 15888, + 15887, + 15901, + 15926, + 15919, + 15910, + 15904, + 15911, + 15912, + 15921, + 15923, + 15904, + 15911, + 15910, + 15883, + 15962, + 15883, + 15879, + 15918, + 16069, + 16114, + 16126, + 16091, + 16114, + 16156, + 16148, + 16070, + 16073, + 16150, + 16101, + 16016, + 16017, + 16027 + ], + "yaxis": "y3" + } + ], + "layout": { + "height": 600, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Backtest date range close prices" + }, + "width": 800, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "Date" + } + }, + "xaxis2": { + "anchor": "y2", + "domain": [ + 0, + 1 ], - "type": "scatter", - "xaxis": "x", - "yaxis": "y" + "title": { + "text": "Date" + } + }, + "xaxis3": { + "anchor": "y3", + "domain": [ + 0, + 1 + ], + "title": { + "text": "Date" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0.68, + 1 + ], + "title": { + "text": "Prices down turn" + } + }, + "yaxis2": { + "anchor": "x2", + "domain": [ + 0.34, + 0.66 + ], + "title": { + "text": "Prices up turn" + } }, + "yaxis3": { + "anchor": "x3", + "domain": [ + 0, + 0.32 + ], + "title": { + "text": "Prices side way" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from investing_algorithm_framework import create_app, BacktestDateRange, \\\n", + " CCXTOHLCVMarketDataSource, RESOURCE_DIRECTORY, \\\n", + " PortfolioConfiguration, convert_polars_to_pandas\n", + "from plotly import graph_objects as go\n", + "from pandas import DataFrame as PandasDataFrame\n", + "from datetime import datetime\n", + "\n", + "# Define the backtest date ranges\n", + "up_turn_date_range = BacktestDateRange(\n", + " start_date=datetime(2022, 12, 20),\n", + " end_date=datetime(2023, 6, 1),\n", + " name=\"up_turn\"\n", + ")\n", + "sideways_date_range = BacktestDateRange(\n", + " start_date=datetime(2022, 6, 10),\n", + " end_date=datetime(2023, 1, 10),\n", + " name=\"sideways\"\n", + ")\n", + "down_turn_date_range = BacktestDateRange(\n", + " start_date=datetime(2021, 12, 21),\n", + " end_date=datetime(2022, 6, 20),\n", + " name=\"down_turn\"\n", + ")\n", + "\n", + "# Create the app and the champion and challenger algorithms\n", + "app = create_app(config={RESOURCE_DIRECTORY: \"resources_dump\"})\n", + "app.add_portfolio_configuration(PortfolioConfiguration(initial_balance=1000, trading_symbol=\"EUR\", market=\"BITVAVO\"))\n", + "\n", + "champion = create_algorithm(\n", + " name=\"primary\",\n", + " description=\"This is the primary algorithm configured with 21 ema and 50 ema\",\n", + " long_period=50,\n", + " short_period=21\n", + ")\n", + "challenger = create_alternative_algorithm(\n", + " name=\"secondary\",\n", + " description=\"This is the primary algorithm configured with 21 ema and 50 ema\",\n", + " long_period=50,\n", + " short_period=21,\n", + " rsi_period=14,\n", + " rsi_sell_threshold=70,\n", + " rsi_buy_threshold=60\n", + ")\n", + "\n", + "def create_prices_graph(\n", + " data: PandasDataFrame, name: str = \"Close\", color: str = \"blue\"\n", + ") -> go.Scatter:\n", + " return go.Scatter(\n", + " x=data.index,\n", + " y=data['Close'],\n", + " mode='lines',\n", + " line=dict(color=color, width=1),\n", + " name=name\n", + " )\n", + "\n", + "down_turn_data_source = CCXTOHLCVMarketDataSource(\n", + " window_size=100,\n", + " time_frame=\"2h\",\n", + " market=\"bitvavo\",\n", + " symbol=\"BTC/EUR\",\n", + " identifier=\"BTC/EUR_OHLCV\"\n", + ")\n", + "df_down_turn = down_turn_data_source.get_data(\n", + " start_date=down_turn_date_range.start_date,\n", + " end_date=down_turn_date_range.end_date\n", + ")\n", + "df_down_turn = convert_polars_to_pandas(df_down_turn)\n", + "up_turn_data_source = CCXTOHLCVMarketDataSource(\n", + " window_size=100,\n", + " time_frame=\"2h\",\n", + " market=\"bitvavo\",\n", + " symbol=\"BTC/EUR\",\n", + " identifier=\"BTC/EUR_OHLCV\"\n", + ")\n", + "df_up_turn = up_turn_data_source.get_data(\n", + " start_date=up_turn_date_range.start_date,\n", + " end_date=up_turn_date_range.end_date\n", + ")\n", + "df_up_turn = convert_polars_to_pandas(df_up_turn)\n", + "side_way_data_source = CCXTOHLCVMarketDataSource(\n", + " window_size=100,\n", + " time_frame=\"2h\",\n", + " market=\"bitvavo\",\n", + " symbol=\"BTC/EUR\",\n", + " identifier=\"BTC/EUR_OHLCV\"\n", + ")\n", + "df_side_way = side_way_data_source.get_data(\n", + " start_date=sideways_date_range.start_date,\n", + " end_date=sideways_date_range.end_date\n", + ")\n", + "df_side_way = convert_polars_to_pandas(df_side_way)\n", + "\n", + "from plotly.subplots import make_subplots\n", + "fig = make_subplots(rows=3, cols=1, shared_xaxes=False, vertical_spacing=0.02)\n", + "fig.add_trace(create_prices_graph(df_down_turn, name=\"Close down turn\", color=\"red\"), row=1, col=1)\n", + "fig.add_trace(create_prices_graph(df_up_turn, name=\"Close up turn\", color=\"green\"), row=2, col=1)\n", + "fig.add_trace(create_prices_graph(df_side_way, name=\"Close side ways\", color=\"blue\"), row=3, col=1)\n", + "\n", + "fig.update_layout(\n", + " height=600, width=800, title_text=\"Backtest date range close prices\"\n", + ")\n", + "\n", + "# Add titles to each subplot\n", + "fig.update_xaxes(title_text=\"Date\", row=1, col=1)\n", + "fig.update_yaxes(title_text=\"Prices down turn\", row=1, col=1)\n", + "fig.update_xaxes(title_text=\"Date\", row=2, col=1)\n", + "fig.update_yaxes(title_text=\"Prices up turn\", row=2, col=1)\n", + "fig.update_xaxes(title_text=\"Date\", row=3, col=1)\n", + "fig.update_yaxes(title_text=\"Prices side way\", row=3, col=1)\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Step 3: Running the backtests for all the date ranges" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92mdown_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 for a total of 2 algorithms.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 73.83it/s]\n", + "Running backtest for algorithm with name primary: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:21<00:00, 102.26it/s]\n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 321.02it/s]\n", + "Running backtest for algorithm with name secondary: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:18<00:00, 119.51it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92mup_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 for a total of 2 algorithms.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 113.81it/s]\n", + "Running backtest for algorithm with name primary: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:21<00:00, 92.46it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 198.18it/s]\n", + "Running backtest for algorithm with name secondary: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:18<00:00, 103.83it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92msideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 for a total of 2 algorithms.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 194.67it/s]\n", + "Running backtest for algorithm with name primary: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:29<00:00, 86.08it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 286.86it/s]\n", + "Running backtest for algorithm with name secondary: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:29<00:00, 86.29it/s] \n" + ] + } + ], + "source": [ + "reports = app.run_backtests(\n", + " algorithms=[champion, challenger],\n", + " date_ranges=[\n", + " down_turn_date_range, up_turn_date_range, sideways_date_range\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Step 4: Analyzing the backtest results" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " :%%%#+- .=*#%%% \u001b[92mBacktest reports evaluation\u001b[0m\n", + " *%%%%%%%+------=*%%%%%%%- \u001b[92m---------------------------\u001b[0m\n", + " *%%%%%%%%%%%%%%%%%%%%%%%- \u001b[93mNumber of reports:\u001b[0m \u001b[92m6 backtest reports\u001b[0m\n", + " .%%%%%%%%%%%%%%%%%%%%%%# \u001b[93mLargest overall profit:\u001b[0m\u001b[92m\u001b[0m\u001b[92m (Algorithm primary) 119.9648 EUR 11.9965% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001b[0m\n", + " #%%%####%%%%%%%%**#%%%+ \u001b[93mLargest overall growth:\u001b[0m\u001b[92m (Algorithm primary) 125.2748 EUR 12.5275% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001b[0m\n", + " .:-+*%%%%- \u001b[95m-+..#\u001b[0m%%%+.\u001b[95m+- +\u001b[0m%%%#*=-:\n", + " .:-=*%%%%. \u001b[95m+=\u001b[0m .%%# \u001b[95m-+.-\u001b[0m%%%%=-:..\n", + " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-:\n", + " +%%%%%%%%%%%%%%%%%%%=\n", + " :++ .=#%%%%%%%%%%%%%*-\n", + " :++: :+%%%%%%#-.\n", + " :++: .%%%%%#=\n", + " :++: .#%%%%%#*=\n", + " :++- :%%%%%%%%%+=\n", + " .++- -%%%%%%%%%%%+=\n", + " .++- .%%%%%%%%%%%%%+=\n", + " .++- *%%%%%%%%%%%%%*+:\n", + " .++- %%%%%%%%%%%%%%#+=\n", + " =++........:::%%%%%%%%%%%%%%*+-\n", + " .=++++++++++**#%%%%%%%%%%%%%++.\n", + " \n", + "\u001b[93mDate ranges of backtests:\u001b[0m\n", + "\u001b[92mdown_turn: 2021-12-21 00:00:00 - 2022-06-20 00:00:00\u001b[0m\n", + "\u001b[92mup_turn: 2022-12-20 00:00:00 - 2023-06-01 00:00:00\u001b[0m\n", + "\u001b[92msideways: 2022-06-10 00:00:00 - 2023-01-10 00:00:00\u001b[0m\n", + "\n", + "\u001b[93mPrice noise\u001b[0m\n", + "\n", + "\u001b[93mAll profits ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Profit │ Profit percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ 119.9648 EUR │ 11.9965% │ 26% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1125.27 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ 70.5835 EUR │ 7.0584% │ 100% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1074.64 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ -2.6223 EUR │ -0.2622% │ 33% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 941.806 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ -8.2249 EUR │ -0.8225% │ 32% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.944 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ -81.3662 EUR │ -8.1366% │ 50% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 918.634 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ -95.0346 EUR │ -9.5035% │ 19% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 904.965 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mAll growths ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Growth │ Growth percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ 125.2748 EUR │ 12.5275% │ 26% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1125.27 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ 74.6376 EUR │ 7.4638% │ 100% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1074.64 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ -2.0557 EUR │ -0.2056% │ 32% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.944 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ -58.1943 EUR │ -5.8194% │ 33% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 941.806 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ secondary │ -81.3663 EUR │ -8.1366% │ 50% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 918.634 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary │ -95.0346 EUR │ -9.5035% │ 19% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 904.965 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n" + ] + } + ], + "source": [ + "from investing_algorithm_framework import pretty_print_backtest_reports_evaluation, BacktestReportsEvaluation\n", + "\n", + "evaluation = BacktestReportsEvaluation(reports)\n", + "pretty_print_backtest_reports_evaluation(evaluation)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Step 5: Highlighting the winning algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "import plotly.graph_objects as go\n", + "from pandas import DataFrame as PandasDataFrame\n", + "\n", + "from investing_algorithm_framework import Trade\n", + "\n", + "def create_entry_graph(\n", + " data: PandasDataFrame,\n", + " trades: List[Trade],\n", + " column = \"Buy\",\n", + " value_lookup_column = \"Close\",\n", + " color = \"green\"\n", + "):\n", + "\n", + " for trade in trades:\n", + " data.loc[trade.opened_at, column] = 1\n", + "\n", + " # Filter the data for buy and sell signals\n", + " buy_signals = data[data[column] == 1]\n", + "\n", + " # Add Buy signals (green circles)\n", + " return go.Scatter(\n", + " x=buy_signals.index,\n", + " y=buy_signals[value_lookup_column],\n", + " mode='markers',\n", + " name='Buy Signal',\n", + " marker=dict(symbol='circle', size=10, color=color)\n", + " )\n", + "\n", + "\n", + "def create_exit_graph(\n", + " data: PandasDataFrame,\n", + " trades: List[Trade],\n", + " column = \"Sell\",\n", + " value_lookup_column = \"Close\",\n", + " color = \"red\"\n", + "):\n", + "\n", + " for trade in trades:\n", + " data.loc[trade.closed_at, column] = 1\n", + "\n", + " # Filter the data for buy and sell signals\n", + " buy_signals = data[data[column] == 1]\n", + "\n", + " # Add Buy signals (green circles)\n", + " return go.Scatter(\n", + " x=buy_signals.index,\n", + " y=buy_signals[value_lookup_column],\n", + " mode='markers',\n", + " name='Sell Signal',\n", + " marker=dict(symbol='circle', size=10, color=color)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The winning algorithm is primary\n", + "\n", + " :%%%#+- .=*#%%% \u001b[92mBacktest report\u001b[0m\n", + " *%%%%%%%+------=*%%%%%%%- \u001b[92m---------------------------\u001b[0m\n", + " *%%%%%%%%%%%%%%%%%%%%%%%- \u001b[93mStart date:\u001b[0m\u001b[92m 2022-12-20 00:00:00\u001b[0m\n", + " .%%%%%%%%%%%%%%%%%%%%%%# \u001b[93mEnd date:\u001b[0m\u001b[92m 2023-06-01 00:00:00\u001b[0m\n", + " #%%%####%%%%%%%%**#%%%+ \u001b[93mNumber of days:\u001b[0m\u001b[92m\u001b[0m\u001b[92m 163\u001b[0m\n", + " .:-+*%%%%- \u001b[95m-+..#\u001b[0m%%%+.\u001b[95m+- +\u001b[0m%%%#*=-: \u001b[93mNumber of runs:\u001b[0m\u001b[92m 1957\u001b[0m\n", + " .:-=*%%%%. \u001b[95m+=\u001b[0m .%%# \u001b[95m-+.-\u001b[0m%%%%=-:.. \u001b[93mNumber of orders:\u001b[0m\u001b[92m 39\u001b[0m\n", + " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \u001b[93mInitial balance:\u001b[0m\u001b[92m 1000.0\u001b[0m\n", + " +%%%%%%%%%%%%%%%%%%%= \u001b[93mFinal balance:\u001b[0m\u001b[92m 1125.2748\u001b[0m\n", + " :++ .=#%%%%%%%%%%%%%*- \u001b[93mTotal net gain:\u001b[0m\u001b[92m 119.9648 12.0%\u001b[0m\n", + " :++: :+%%%%%%#-. \u001b[93mGrowth:\u001b[0m\u001b[92m 125.2748 12.53%\u001b[0m\n", + " :++: .%%%%%#= \u001b[93mNumber of trades closed:\u001b[0m\u001b[92m 1\u001b[0m\n", + " :++: .#%%%%%#*= \u001b[93mNumber of trades open(end of backtest):\u001b[0m\u001b[92m 1\u001b[0m\n", + " :++- :%%%%%%%%%+= \u001b[93mPercentage positive trades:\u001b[0m\u001b[92m 26.31578947368421%\u001b[0m\n", + " .++- -%%%%%%%%%%%+= \u001b[93mPercentage negative trades:\u001b[0m\u001b[92m 73.68421052631578%\u001b[0m\n", + " .++- .%%%%%%%%%%%%%+= \u001b[93mAverage trade size:\u001b[0m\u001b[92m 248.6320 EUR\u001b[0m\n", + " .++- *%%%%%%%%%%%%%*+: \u001b[93mAverage trade duration:\u001b[0m\u001b[92m 106.10526315789474 hours\u001b[0m\n", + " .++- %%%%%%%%%%%%%%#+=\n", + " =++........:::%%%%%%%%%%%%%%*+-\n", + " .=++++++++++**#%%%%%%%%%%%%%++.\n", + " \n", + "\u001b[93mPositions overview\u001b[0m\n", + "╭────────────┬──────────┬──────────────────────┬───────────────────────┬──────────────┬───────────────┬───────────────────────────┬────────────────┬───────────────╮\n", + "│ Position │ Amount │ Pending buy amount │ Pending sell amount │ Cost (EUR) │ Value (EUR) │ Percentage of portfolio │ Growth (EUR) │ Growth_rate │\n", + "├────────────┼──────────┼──────────────────────┼───────────────────────┼──────────────┼───────────────┼───────────────────────────┼────────────────┼───────────────┤\n", + "│ EUR │ 870.75 │ 0 │ 0 │ 870.75 │ 870.75 │ 77.3811% │ 0 │ 0.0000% │\n", + "├────────────┼──────────┼──────────────────────┼───────────────────────┼──────────────┼───────────────┼───────────────────────────┼────────────────┼───────────────┤\n", + "│ BTC │ 0.01 │ 0 │ 0 │ 249.215 │ 254.525 │ 22.6189% │ 5.31 │ 2.1307% │\n", + "╰────────────┴──────────┴──────────────────────┴───────────────────────┴──────────────┴───────────────┴───────────────────────────┴────────────────┴───────────────╯\n", + "\u001b[93mTrades overview\u001b[0m\n", + "╭─────────┬─────────────────────┬─────────────────────┬────────────────────┬──────────────┬─────────────────────┬───────────────────────┬────────────────────┬─────────────────────────────┬───────────────────────╮\n", + "│ Pair │ Open date │ Close date │ Duration (hours) │ Cost (EUR) │ Net gain (EUR) │ Net gain percentage │ Open price (EUR) │ Last reported price (EUR) │ Stop loss triggered │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2022-12-26 02:00:00 │ 2022-12-26 22:00:00 │ 20:00:00 │ 249.489 │ -1.1461 │ -0.4594% │ 15891 │ 15815 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2022-12-26 22:00:00 │ 2022-12-27 06:00:00 │ 8:00:00 │ 248.814 │ -0.3690 │ -0.1483% │ 15848 │ 15802 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-01-02 12:00:00 │ 2023-01-31 06:00:00 │ 28 days, 18:00:00 │ 248.716 │ 85.8918 │ 34.5341% │ 15642.5 │ 21000 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-02-01 22:00:00 │ 2023-02-05 20:00:00 │ 3 days, 22:00:00 │ 248.222 │ -4.5597 │ -1.8370% │ 21584.5 │ 21216 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-02-07 22:00:00 │ 2023-02-09 02:00:00 │ 1 day, 4:00:00 │ 248.745 │ -2.3518 │ -0.9454% │ 21630 │ 21378 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-02-15 08:00:00 │ 2023-02-22 16:00:00 │ 7 days, 8:00:00 │ 249.677 │ 21.8647 │ 8.7572% │ 20634.5 │ 22338 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-03-13 02:00:00 │ 2023-03-23 14:00:00 │ 10 days, 12:00:00 │ 248.02 │ 53.3655 │ 21.5166% │ 20842 │ 25207 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-03-23 14:00:00 │ 2023-03-25 12:00:00 │ 1 day, 22:00:00 │ 247.92 │ -2.3376 │ -0.9429% │ 25825 │ 25581 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-03-26 12:00:00 │ 2023-03-27 16:00:00 │ 1 day, 4:00:00 │ 249.883 │ -8.2176 │ -3.2886% │ 26029.5 │ 24973 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-03-29 14:00:00 │ 2023-04-03 08:00:00 │ 4 days, 18:00:00 │ 248.587 │ -4.1040 │ -1.6509% │ 26167 │ 25796 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-04-05 10:00:00 │ 2023-04-05 16:00:00 │ 6:00:00 │ 247.622 │ -2.5460 │ -1.0282% │ 26065.5 │ 25718 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-04-09 22:00:00 │ 2023-04-17 12:00:00 │ 7 days, 14:00:00 │ 247.484 │ 9.6283 │ 3.8904% │ 26051 │ 26973 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-04-18 20:00:00 │ 2023-04-19 12:00:00 │ 16:00:00 │ 248.656 │ -7.7445 │ -3.1145% │ 27628.5 │ 26844 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-04-26 06:00:00 │ 2023-05-01 12:00:00 │ 5 days, 6:00:00 │ 248.05 │ 1.2336 │ 0.4973% │ 25838.5 │ 25948 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-04 08:00:00 │ 2023-05-08 00:00:00 │ 3 days, 16:00:00 │ 249.845 │ -2.5033 │ -1.0019% │ 26299.5 │ 25868 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-15 06:00:00 │ 2023-05-17 06:00:00 │ 2 days, 0:00:00 │ 247.626 │ -3.6897 │ -1.4900% │ 25268 │ 24851 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-17 20:00:00 │ 2023-05-19 12:00:00 │ 1 day, 16:00:00 │ 249.822 │ -3.6036 │ -1.4425% │ 25234.5 │ 24865 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-20 22:00:00 │ 2023-05-21 18:00:00 │ 20:00:00 │ 247.926 │ -1.6335 │ -0.6589% │ 25043 │ 24858 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-23 04:00:00 │ 2023-05-24 14:00:00 │ 1 day, 10:00:00 │ 248.322 │ -7.2128 │ -2.9046% │ 25339 │ 24579 │ False │\n", + "├─────────┼─────────────────────┼─────────────────────┼────────────────────┼──────────────┼─────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────┼───────────────────────┤\n", + "│ BTC-EUR │ 2023-05-27 02:00:00 │ │ 4 days, 22:00:00 │ 249.215 │ 4.3650 (unrealized) │ 1.7515% (unrealized) │ 24921.5 │ 25358 │ False │\n", + "╰─────────┴─────────────────────┴─────────────────────┴────────────────────┴──────────────┴─────────────────────┴───────────────────────┴────────────────────┴─────────────────────────────┴───────────────────────╯\n" + ] + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ { - "marker": { - "color": "red", - "symbol": "arrow-down" + "line": { + "color": "blue", + "width": 1 }, - "mode": "markers", - "name": "Sell", + "mode": "lines", + "name": "Close price up turn", + "type": "scatter", "x": [ "2022-12-20T00:00:00", "2022-12-20T02:00:00", @@ -24537,2044 +16987,2163 @@ "2023-05-31T18:00:00", "2023-05-31T20:00:00", "2023-05-31T22:00:00", - "2023-06-01T00:00:00" + "2023-06-01T00:00:00", + null ], + "xaxis": "x", "y": [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 15897.0, - null, - null, - null, - 15821.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 21206.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 21248.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 20992.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 22352.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26296.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25679.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25000.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26128.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25755.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26894.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 26801.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25865.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 25639.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 24781.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 24903.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 24901.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 24458.0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + 15603, + 15748, + 15831, + 15832, + 15801, + 15795, + 15801, + 15906, + 15839, + 15898, + 15877, + 15906, + 15868, + 15858, + 15839, + 15818, + 15901, + 15916, + 15846, + 15870, + 15867, + 15774, + 15837, + 15860, + 15820, + 15832, + 15811, + 15805, + 15828, + 15857, + 15793, + 15692, + 15678, + 15734, + 15843, + 15861, + 15825, + 15859, + 15857, + 15850, + 15866, + 15873, + 15883, + 15863, + 15865, + 15869, + 15839, + 15773, + 15832, + 15847, + 15814, + 15854, + 15854, + 15818, + 15809, + 15845, + 15848, + 15847, + 15834, + 15822, + 15826, + 15823, + 15843, + 15834, + 15854, + 15820, + 15830, + 15819, + 15815, + 15805, + 15842, + 15831, + 15879, + 15912, + 15848, + 15827, + 15837, + 15866, + 15842, + 15811, + 15837, + 15836, + 15815, + 15897, + 15820, + 15824, + 15802, + 15821, + 15807, + 15786, + 15823, + 15755, + 15675, + 15678, + 15700, + 15707, + 15673, + 15658, + 15592, + 15637, + 15631, + 15650, + 15619, + 15585, + 15647, + 15622, + 15550, + 15569, + 15544, + 15560, + 15606, + 15579, + 15578, + 15599, + 15578, + 15561, + 15532, + 15562, + 15565, + 15580, + 15585, + 15581, + 15529, + 15473, + 15501, + 15452, + 15438, + 15523, + 15415, + 15454, + 15497, + 15518, + 15479, + 15451, + 15452, + 15476, + 15445, + 15461, + 15510, + 15504, + 15489, + 15470, + 15444, + 15439, + 15437, + 15433, + 15425, + 15426, + 15439, + 15465, + 15462, + 15442, + 15466, + 15504, + 15516, + 15533, + 15483, + 15569, + 15549, + 15661, + 15653, + 15660, + 15629, + 15692, + 15666, + 15682, + 15682, + 15605, + 15639, + 15621, + 15637, + 15710, + 15859, + 15874, + 15833, + 15790, + 15763, + 15729, + 15770, + 15784, + 15803, + 15929, + 15921, + 15897, + 15869, + 15869, + 15856, + 15889, + 15936, + 15872, + 15849, + 15896, + 15838, + 15866, + 15863, + 15847, + 15797, + 15848, + 15895, + 16001, + 15970, + 16001, + 15992, + 16001, + 15975, + 15991, + 15988, + 15966, + 15953, + 15944, + 15887, + 15871, + 15823, + 15888, + 15891, + 15924, + 15926, + 15922, + 15910, + 15929, + 15888, + 15887, + 15901, + 15926, + 15919, + 15910, + 15904, + 15911, + 15912, + 15921, + 15923, + 15904, + 15911, + 15910, + 15883, + 15962, + 15883, + 15879, + 15918, + 16069, + 16114, + 16126, + 16091, + 16114, + 16156, + 16148, + 16070, + 16073, + 16150, + 16101, + 16016, + 16017, + 16027, + 16035, + 16043, + 16010, + 16050, + 16065, + 16074, + 16144, + 16135, + 16222, + 16287, + 16235, + 16282, + 16240, + 16171, + 16238, + 16245, + 16230, + 16210, + 16124, + 16171, + 16299, + 16307, + 16635, + 16923, + 16922, + 16795, + 16836, + 16875, + 16914, + 16896, + 16736, + 17360, + 17430, + 17350, + 17374, + 17297, + 17389, + 17342, + 17347, + 17462, + 17515, + 17482, + 17816, + 17800, + 17880, + 18281, + 18384, + 19320, + 19309, + 19211, + 19301, + 18935, + 19122, + 19328, + 19164, + 19137, + 19170, + 19260, + 19347, + 19157, + 19081, + 19129, + 19092, + 19136, + 19102, + 19068, + 19281, + 19301, + 19243, + 19301, + 19277, + 19496, + 19412, + 19533, + 19500, + 19244, + 19237, + 19251, + 19381, + 19524, + 19700, + 19510, + 19566, + 19466, + 19529, + 19541, + 19536, + 19595, + 19579, + 19606, + 19628, + 19694, + 19763, + 19751, + 19610, + 19675, + 19759, + 19783, + 19696, + 19548, + 19616, + 19722, + 19436, + 19338, + 19347, + 19225, + 19133, + 19171, + 19185, + 19278, + 19224, + 19205, + 19131, + 19233, + 19337, + 19265, + 19488, + 19338, + 19447, + 19499, + 19465, + 19367, + 19339, + 19365, + 19340, + 19487, + 19540, + 19678, + 19797, + 20531, + 20858, + 20779, + 20757, + 20790, + 20848, + 21235, + 21113, + 21118, + 21186, + 21426, + 21469, + 21376, + 20990, + 21030, + 20981, + 21108, + 21128, + 21088, + 20985, + 21121, + 20997, + 21131, + 20936, + 20794, + 20912, + 20868, + 20793, + 20876, + 20813, + 20884, + 21062, + 21005, + 21046, + 21230, + 21003, + 21145, + 21078, + 21242, + 21234, + 21262, + 21160, + 21053, + 21089, + 21054, + 21068, + 21032, + 21147, + 21054, + 20799, + 20665, + 20764, + 20817, + 20897, + 20777, + 20784, + 20771, + 20693, + 20707, + 20854, + 21615, + 21126, + 21217, + 21176, + 21226, + 21045, + 21070, + 21108, + 21216, + 21151, + 21136, + 21173, + 21196, + 21112, + 20772, + 20957, + 21162, + 21206, + 21083, + 21110, + 21078, + 21188, + 21365, + 21389, + 21248, + 21257, + 21241, + 21304, + 21243, + 21182, + 21160, + 21168, + 21169, + 21194, + 21176, + 21224, + 21172, + 21212, + 21380, + 21337, + 21400, + 21369, + 21421, + 21540, + 21665, + 21628, + 21693, + 21988, + 21913, + 21840, + 21766, + 21810, + 21835, + 21785, + 21251, + 21191, + 21176, + 21341, + 21296, + 21000, + 20977, + 21067, + 21050, + 21062, + 21000, + 21206, + 21138, + 21112, + 21308, + 21303, + 21318, + 21317, + 21132, + 21311, + 21282, + 21293, + 21270, + 21213, + 21141, + 21172, + 21180, + 21058, + 21092, + 21321, + 21550, + 21550, + 21752, + 21632, + 21611, + 21625, + 21646, + 21646, + 21714, + 21812, + 21909, + 21827, + 21507, + 21532, + 21638, + 21600, + 21578, + 21532, + 21445, + 21554, + 21553, + 21730, + 21731, + 21562, + 21671, + 21687, + 21659, + 21600, + 21601, + 21622, + 21634, + 21655, + 21758, + 21695, + 21729, + 21711, + 21671, + 21626, + 21616, + 21623, + 21656, + 21680, + 21661, + 21654, + 21491, + 21398, + 21169, + 21216, + 21248, + 21269, + 21301, + 21229, + 21099, + 21250, + 21227, + 21239, + 21209, + 21459, + 21499, + 21453, + 21369, + 21216, + 21248, + 21298, + 21341, + 21354, + 21481, + 21462, + 21495, + 21400, + 21616, + 21546, + 21604, + 21661, + 21673, + 21670, + 21630, + 21576, + 21552, + 21570, + 21525, + 21332, + 21435, + 21339, + 21425, + 21445, + 21378, + 20992, + 21073, + 21137, + 21130, + 21081, + 21059, + 21012, + 20974, + 20522, + 20348, + 20303, + 20406, + 20349, + 20329, + 20400, + 20400, + 20318, + 20371, + 20296, + 20346, + 20392, + 20180, + 20275, + 20275, + 20320, + 20318, + 20333, + 20334, + 20334, + 20392, + 20350, + 20322, + 20312, + 20468, + 20505, + 20446, + 20409, + 20437, + 20428, + 20551, + 20522, + 20475, + 20573, + 20623, + 20651, + 20385, + 20421, + 20342, + 20470, + 20471, + 20493, + 20240, + 20231, + 20200, + 20139, + 20040, + 20160, + 20148, + 20302, + 20223, + 20225, + 20251, + 20304, + 20281, + 20294, + 20199, + 20595, + 20547, + 20704, + 20709, + 20690, + 20597, + 20601, + 20668, + 20629, + 20679, + 20950, + 21220, + 21370, + 21370, + 21799, + 22596, + 22714, + 22969, + 23072, + 23014, + 22929, + 22992, + 22947, + 22852, + 23500, + 23268, + 23264, + 22982, + 22043, + 22395, + 22378, + 22286, + 22224, + 22334, + 22393, + 22360, + 22653, + 22754, + 22936, + 22861, + 22970, + 23068, + 22987, + 23049, + 22925, + 22971, + 22913, + 22971, + 23065, + 23082, + 23022, + 23018, + 23029, + 23108, + 23049, + 23127, + 22982, + 22981, + 23083, + 23089, + 23265, + 22780, + 22951, + 22961, + 22724, + 22715, + 22867, + 22921, + 22905, + 23265, + 23277, + 23242, + 23315, + 23221, + 23243, + 23186, + 23250, + 23299, + 23369, + 23363, + 23436, + 23261, + 23066, + 23075, + 23000, + 23156, + 23126, + 22748, + 22974, + 22715, + 22721, + 22634, + 22582, + 22674, + 22746, + 22669, + 22338, + 22352, + 22463, + 22440, + 22816, + 22787, + 23028, + 22950, + 22936, + 22929, + 22467, + 22644, + 22590, + 22537, + 22650, + 22556, + 22602, + 22652, + 22628, + 22573, + 22530, + 22543, + 22586, + 22526, + 22176, + 21906, + 21965, + 21936, + 22009, + 22016, + 21904, + 21881, + 21943, + 21899, + 21801, + 21812, + 21857, + 21875, + 21808, + 21822, + 22067, + 21989, + 22103, + 22000, + 22046, + 22178, + 22130, + 22082, + 22146, + 22085, + 22362, + 22389, + 22335, + 22333, + 22334, + 22194, + 22209, + 22143, + 22198, + 22416, + 22247, + 22034, + 21967, + 22035, + 22142, + 22153, + 22167, + 22104, + 21937, + 21953, + 22068, + 22030, + 22180, + 22181, + 21991, + 21895, + 21885, + 21986, + 22143, + 22357, + 22269, + 22330, + 22249, + 22205, + 22210, + 22237, + 21929, + 22089, + 22163, + 22089, + 22090, + 22036, + 21980, + 22042, + 22030, + 22028, + 22010, + 21992, + 22135, + 22102, + 22115, + 20961, + 21075, + 21086, + 21121, + 21104, + 21076, + 21116, + 21098, + 21104, + 21035, + 20917, + 21031, + 21026, + 21039, + 21017, + 21034, + 21007, + 21043, + 21047, + 20998, + 20979, + 20931, + 20864, + 21008, + 21256, + 21051, + 21092, + 21071, + 21052, + 21066, + 21148, + 21124, + 21094, + 21093, + 21171, + 21126, + 21073, + 21046, + 21009, + 21030, + 21079, + 21010, + 21056, + 21139, + 21049, + 20971, + 20972, + 20962, + 21051, + 21000, + 21022, + 20977, + 20996, + 20965, + 20996, + 21065, + 21112, + 20968, + 20913, + 21042, + 21019, + 21024, + 20849, + 20812, + 20887, + 20948, + 20859, + 20983, + 20895, + 20853, + 20845, + 20587, + 20593, + 20641, + 20577, + 20520, + 20475, + 20482, + 20518, + 20446, + 20269, + 19683, + 19145, + 19257, + 18984, + 18946, + 18903, + 18843, + 18779, + 18673, + 19026, + 18760, + 18697, + 18844, + 18902, + 18989, + 19567, + 19342, + 19277, + 18786, + 18949, + 19034, + 18942, + 18888, + 19017, + 19050, + 19155, + 19219, + 19217, + 19181, + 19200, + 19195, + 19216, + 19237, + 19209, + 19257, + 19638, + 19577, + 19974, + 20665, + 20993, + 20775, + 20880, + 20891, + 20562, + 20616, + 21000, + 22323, + 22407, + 22505, + 22192, + 22338, + 22600, + 22678, + 22547, + 22488, + 22613, + 23072, + 23972, + 24160, + 23810, + 23275, + 22855, + 23000, + 23137, + 22987, + 23006, + 23194, + 22994, + 23422, + 23514, + 23244, + 23036, + 23052, + 23163, + 22973, + 22982, + 22945, + 23000, + 23212, + 23257, + 23469, + 23433, + 23475, + 23324, + 23590, + 23523, + 23605, + 24196, + 24237, + 24200, + 24511, + 24721, + 25360, + 24911, + 24863, + 24884, + 25192, + 25426, + 25656, + 25591, + 25620, + 25879, + 25699, + 25708, + 25765, + 25732, + 25518, + 25591, + 25678, + 25568, + 25245, + 25419, + 25539, + 25411, + 25280, + 25350, + 25492, + 25506, + 25854, + 26222, + 26549, + 26259, + 26264, + 25987, + 25661, + 25882, + 26519, + 26484, + 26441, + 26025, + 25908, + 25882, + 25967, + 26166, + 25952, + 26091, + 25968, + 26048, + 25756, + 26098, + 26103, + 26100, + 26100, + 26055, + 26200, + 26083, + 26173, + 26158, + 26200, + 26279, + 26160, + 26119, + 26132, + 26340, + 26535, + 26451, + 24551, + 25010, + 25158, + 25005, + 25207, + 25443, + 25420, + 25450, + 25447, + 25207, + 26296, + 26075, + 26216, + 26042, + 26145, + 26121, + 26102, + 26157, + 26130, + 26149, + 25769, + 25967, + 26064, + 25672, + 25921, + 25508, + 25596, + 25615, + 25686, + 25588, + 25622, + 25596, + 25581, + 25679, + 25719, + 25604, + 25323, + 25562, + 25570, + 25708, + 25618, + 25631, + 25619, + 25740, + 25940, + 26165, + 25902, + 25922, + 25908, + 25911, + 25968, + 25864, + 25901, + 25725, + 25873, + 25956, + 25886, + 25697, + 24973, + 25000, + 25003, + 25180, + 25140, + 25062, + 24966, + 24972, + 24920, + 24880, + 25012, + 24832, + 24911, + 24798, + 25268, + 25063, + 25187, + 25230, + 25256, + 25449, + 25892, + 26044, + 26073, + 26175, + 26240, + 26018, + 26198, + 26089, + 26155, + 26185, + 26256, + 26394, + 26493, + 26341, + 26343, + 26122, + 25999, + 25579, + 25686, + 25775, + 25760, + 25833, + 25871, + 25757, + 25510, + 25596, + 25714, + 26017, + 26194, + 26113, + 26237, + 26269, + 26233, + 26388, + 26302, + 26327, + 26207, + 26231, + 26200, + 26205, + 26212, + 26192, + 26161, + 26296, + 26234, + 26211, + 26245, + 26259, + 26195, + 26216, + 26156, + 26103, + 25979, + 26061, + 25837, + 25868, + 26082, + 25668, + 25756, + 25701, + 25796, + 26128, + 25984, + 25986, + 25810, + 25745, + 25793, + 25448, + 25507, + 25520, + 25656, + 25644, + 25662, + 25828, + 25942, + 25753, + 25622, + 25790, + 25746, + 25782, + 25711, + 26066, + 26029, + 26100, + 26041, + 26044, + 26108, + 25987, + 25718, + 25755, + 25926, + 25856, + 25857, + 25780, + 25855, + 25766, + 25615, + 25593, + 25630, + 25624, + 25717, + 25674, + 25714, + 25678, + 25715, + 25735, + 25722, + 25672, + 25535, + 25571, + 25534, + 25673, + 25662, + 25632, + 25622, + 25600, + 25629, + 25638, + 25731, + 25818, + 25812, + 25741, + 25754, + 25742, + 25722, + 25689, + 25634, + 25658, + 25685, + 25821, + 25787, + 25750, + 25650, + 25651, + 25677, + 25665, + 25650, + 25644, + 25811, + 26090, + 25952, + 25970, + 25961, + 25989, + 25967, + 25980, + 26034, + 26011, + 26236, + 26837, + 26853, + 26889, + 27264, + 27668, + 27647, + 27387, + 27615, + 27545, + 27537, + 27687, + 27700, + 27678, + 27620, + 27692, + 27652, + 27649, + 27414, + 27382, + 27452, + 27490, + 27503, + 27405, + 27317, + 27316, + 27102, + 27258, + 27179, + 27357, + 27379, + 27419, + 27368, + 27413, + 27410, + 27334, + 27567, + 27580, + 27513, + 27451, + 27541, + 27842, + 27730, + 27847, + 27799, + 27854, + 27905, + 27890, + 27554, + 27586, + 27617, + 27730, + 27706, + 27639, + 27639, + 27696, + 27717, + 27748, + 27665, + 27640, + 27610, + 27570, + 27621, + 27576, + 27577, + 27544, + 27611, + 27652, + 27583, + 27548, + 27541, + 27616, + 27585, + 27594, + 27659, + 27625, + 27336, + 27340, + 27321, + 27134, + 27243, + 26973, + 26894, + 26923, + 27003, + 27039, + 27020, + 26978, + 26955, + 26998, + 27100, + 27212, + 27258, + 27680, + 27715, + 27550, + 27389, + 27583, + 27689, + 27691, + 27575, + 27552, + 27635, + 27457, + 26751, + 26844, + 26801, + 26747, + 26790, + 26703, + 26641, + 26343, + 26409, + 26364, + 26383, + 26358, + 26342, + 26113, + 26275, + 25972, + 25955, + 25644, + 25750, + 25757, + 25734, + 25831, + 25681, + 25693, + 25630, + 25552, + 25657, + 25557, + 25403, + 24834, + 24833, + 24793, + 24831, + 24842, + 24951, + 24860, + 24829, + 24880, + 24895, + 25104, + 25341, + 25250, + 25330, + 25503, + 25182, + 25148, + 25238, + 25399, + 25320, + 25280, + 25267, + 25084, + 25131, + 25092, + 25063, + 25136, + 25267, + 25326, + 25247, + 24979, + 24839, + 25034, + 25067, + 24732, + 24800, + 24810, + 24824, + 24876, + 24792, + 24727, + 24794, + 24712, + 24763, + 24869, + 24859, + 24937, + 25203, + 25197, + 25782, + 25795, + 25844, + 25800, + 25868, + 25763, + 26157, + 26188, + 26910, + 26986, + 26920, + 25365, + 25999, + 25735, + 26034, + 26162, + 26324, + 26180, + 26239, + 26238, + 26273, + 26463, + 26576, + 26888, + 26879, + 26735, + 26777, + 26686, + 26781, + 26792, + 26587, + 26678, + 26404, + 26426, + 26547, + 26617, + 26620, + 26588, + 26576, + 26646, + 26659, + 26657, + 26586, + 26619, + 26701, + 26595, + 26486, + 26561, + 26546, + 26529, + 26425, + 26481, + 26568, + 26579, + 26556, + 26514, + 26580, + 26920, + 26887, + 26667, + 26703, + 26526, + 25946, + 25963, + 25913, + 26039, + 25944, + 25948, + 25865, + 25717, + 25801, + 25397, + 25548, + 25603, + 25503, + 25586, + 25521, + 25538, + 25559, + 25630, + 25583, + 25980, + 26003, + 26062, + 26053, + 26032, + 25883, + 25869, + 25860, + 25981, + 25971, + 25874, + 25732, + 25645, + 25847, + 25656, + 26152, + 26182, + 26226, + 26246, + 26373, + 26262, + 26316, + 26338, + 26102, + 26257, + 26161, + 26241, + 26207, + 26161, + 26548, + 26434, + 26453, + 26344, + 26396, + 26462, + 26565, + 26642, + 26763, + 26817, + 26762, + 26783, + 26837, + 26658, + 26631, + 26673, + 26626, + 26609, + 26316, + 26007, + 26198, + 26253, + 26220, + 26256, + 26236, + 26211, + 26233, + 26308, + 26205, + 26229, + 26275, + 26322, + 26291, + 26322, + 26137, + 25868, + 25639, + 25606, + 25659, + 25382, + 25359, + 25358, + 25264, + 25340, + 25106, + 24889, + 25039, + 25197, + 25263, + 25174, + 25067, + 25221, + 25121, + 25272, + 25223, + 25045, + 25213, + 25280, + 25189, + 25217, + 25265, + 25238, + 25255, + 25137, + 25159, + 25275, + 25666, + 25683, + 25093, + 25231, + 25082, + 25156, + 25094, + 25024, + 25106, + 25130, + 25106, + 25080, + 24931, + 24904, + 24687, + 24600, + 24729, + 24748, + 24606, + 24327, + 24084, + 24127, + 24202, + 24214, + 24309, + 24254, + 24234, + 24397, + 24691, + 24695, + 24743, + 24725, + 24662, + 24689, + 24759, + 24731, + 24741, + 24746, + 24745, + 24782, + 24797, + 24708, + 24614, + 24784, + 24746, + 24748, + 24767, + 24714, + 24817, + 24959, + 24847, + 24777, + 24790, + 24825, + 25053, + 25082, + 25170, + 25208, + 25223, + 25152, + 25175, + 25200, + 25284, + 25209, + 25173, + 24984, + 24835, + 24945, + 24893, + 25048, + 24865, + 24854, + 24810, + 24886, + 24923, + 24802, + 24857, + 24888, + 24991, + 24912, + 24851, + 24781, + 24817, + 24600, + 24623, + 24753, + 25009, + 25263, + 25228, + 25260, + 25182, + 25225, + 25119, + 25317, + 25332, + 25322, + 25285, + 25115, + 24611, + 24827, + 24977, + 24893, + 24909, + 24925, + 24942, + 24921, + 24855, + 24865, + 24903, + 24864, + 24875, + 24849, + 24910, + 24878, + 24870, + 24869, + 24876, + 24864, + 24899, + 24893, + 24889, + 24954, + 25120, + 25044, + 25024, + 25089, + 25114, + 25151, + 25077, + 25022, + 25028, + 24836, + 24873, + 24889, + 24858, + 24901, + 24763, + 24711, + 24589, + 24635, + 24762, + 24845, + 24821, + 24798, + 24977, + 24880, + 24831, + 24820, + 24884, + 24842, + 24970, + 25304, + 25360, + 25258, + 25282, + 25359, + 25253, + 25352, + 25229, + 25251, + 25225, + 25274, + 25203, + 24840, + 24837, + 24742, + 24829, + 24819, + 24579, + 24458, + 24438, + 24407, + 24537, + 24500, + 24311, + 24435, + 24525, + 24399, + 24500, + 24518, + 24589, + 24441, + 24566, + 24675, + 24689, + 24695, + 24615, + 24631, + 24545, + 24674, + 24652, + 24619, + 24734, + 24965, + 24909, + 24954, + 24911, + 24910, + 24894, + 24957, + 24909, + 24926, + 24910, + 24908, + 24844, + 24893, + 24945, + 24933, + 24953, + 25047, + 25238, + 25322, + 25309, + 25346, + 25292, + 25300, + 25301, + 25402, + 25470, + 25627, + 25948, + 26162, + 26245, + 26038, + 26053, + 26006, + 26053, + 26056, + 26035, + 25780, + 25856, + 25800, + 25837, + 25915, + 25893, + 25973, + 25960, + 25951, + 26001, + 26092, + 25921, + 25811, + 25866, + 25956, + 25835, + 25820, + 25904, + 25850, + 25332, + 25452, + 25431, + 25389, + 25380, + 25278, + 25388, + 25292, + 25350, + 25469, + 25358, null ], + "yaxis": "y" + }, + { + "marker": { + "color": "green", + "size": 10, + "symbol": "circle" + }, + "mode": "markers", + "name": "Buy Signal", + "type": "scatter", + "x": [ + "2022-12-26T02:00:00", + "2022-12-26T22:00:00", + "2023-01-02T12:00:00", + "2023-02-01T22:00:00", + "2023-02-07T22:00:00", + "2023-02-15T08:00:00", + "2023-03-13T02:00:00", + "2023-03-23T14:00:00", + "2023-03-26T12:00:00", + "2023-03-29T14:00:00", + "2023-04-05T10:00:00", + "2023-04-09T22:00:00", + "2023-04-18T20:00:00", + "2023-04-26T06:00:00", + "2023-05-04T08:00:00", + "2023-05-15T06:00:00", + "2023-05-17T20:00:00", + "2023-05-20T22:00:00", + "2023-05-23T04:00:00", + "2023-05-27T02:00:00" + ], + "xaxis": "x", + "y": [ + 15912, + 15897, + 15629, + 21550, + 21661, + 20679, + 20775, + 26296, + 26165, + 26240, + 26108, + 25952, + 27689, + 25763, + 26316, + 25208, + 25228, + 25089, + 25360, + 24957 + ], + "yaxis": "y" + }, + { + "marker": { + "color": "red", + "size": 10, + "symbol": "circle" + }, + "mode": "markers", + "name": "Sell Signal", "type": "scatter", + "x": [ + "2022-12-26T22:00:00", + "2022-12-27T06:00:00", + "2023-01-31T06:00:00", + "2023-02-05T20:00:00", + "2023-02-09T02:00:00", + "2023-02-22T16:00:00", + "2023-03-23T14:00:00", + "2023-03-25T12:00:00", + "2023-03-27T16:00:00", + "2023-04-03T08:00:00", + "2023-04-05T16:00:00", + "2023-04-17T12:00:00", + "2023-04-19T12:00:00", + "2023-05-01T12:00:00", + "2023-05-08T00:00:00", + "2023-05-17T06:00:00", + "2023-05-19T12:00:00", + "2023-05-21T18:00:00", + "2023-05-24T14:00:00", + null + ], "xaxis": "x", + "y": [ + 15897, + 15821, + 21206, + 21248, + 20992, + 22352, + 26296, + 25679, + 25000, + 26128, + 25755, + 26894, + 26801, + 25865, + 25639, + 24781, + 24903, + 24901, + 24458, + null + ], "yaxis": "y" } ], "layout": { + "height": 600, "template": { "data": { - "histogram2dcontour": [ + "bar": [ { - "type": "histogram2dcontour", - "colorbar": { - "outlinewidth": 0, - "ticks": "" + "error_x": { + "color": "#2a3f5f" }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" } ], "choropleth": [ { - "type": "choropleth", "colorbar": { "outlinewidth": 0, "ticks": "" - } + }, + "type": "choropleth" } ], - "histogram2d": [ + "contour": [ { - "type": "histogram2d", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -26610,22 +19179,31 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] - ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" } ], "heatmap": [ { - "type": "heatmap", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -26661,22 +19239,22 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] - ] + ], + "type": "heatmap" } ], "heatmapgl": [ { - "type": "heatmapgl", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -26712,31 +19290,34 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] - ] + ], + "type": "heatmapgl" } ], - "contourcarpet": [ + "histogram": [ { - "type": "contourcarpet", - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" } ], - "contour": [ + "histogram2d": [ { - "type": "contour", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -26772,22 +19353,22 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] - ] + ], + "type": "histogram2d" } ], - "surface": [ + "histogram2dcontour": [ { - "type": "surface", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -26823,19 +19404,37 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] - ] + ], + "type": "histogram2dcontour" } ], "mesh3d": [ { - "type": "mesh3d", "colorbar": { "outlinewidth": 0, "ticks": "" - } + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" } ], "scatter": [ @@ -26848,162 +19447,149 @@ "type": "scatter" } ], - "parcoords": [ + "scatter3d": [ { - "type": "parcoords", "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } - } - ], - "scatterpolargl": [ - { - "type": "scatterpolargl", + }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } - } - ], - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } }, - "type": "bar" + "type": "scatter3d" } ], - "scattergeo": [ + "scattercarpet": [ { - "type": "scattergeo", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scattercarpet" } ], - "scatterpolar": [ + "scattergeo": [ { - "type": "scatterpolar", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } }, - "type": "histogram" + "type": "scattergeo" } ], "scattergl": [ { - "type": "scattergl", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scattergl" } ], - "scatter3d": [ + "scattermapbox": [ { - "type": "scatter3d", - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scattermapbox" } ], - "scattermapbox": [ + "scatterpolar": [ { - "type": "scattermapbox", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scatterpolar" } ], - "scatterternary": [ + "scatterpolargl": [ { - "type": "scatterternary", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scatterpolargl" } ], - "scattercarpet": [ + "scatterternary": [ { - "type": "scattercarpet", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - } + }, + "type": "scatterternary" } ], - "carpet": [ + "surface": [ { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" + "colorbar": { + "outlinewidth": 0, + "ticks": "" }, - "type": "carpet" + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" } ], "table": [ @@ -27026,84 +19612,15 @@ }, "type": "table" } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } ] }, - "layout": { - "autotypenumbers": "strict", - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "hovermode": "closest", - "hoverlabel": { - "align": "left" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "bgcolor": "#E5ECF6", - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "ternary": { - "bgcolor": "#E5ECF6", - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 }, + "autotypenumbers": "strict", "coloraxis": { "colorbar": { "outlinewidth": 0, @@ -27111,51 +19628,55 @@ } }, "colorscale": { - "sequential": [ + "diverging": [ [ - 0.0, - "#0d0887" + 0, + "#8e0152" ], [ - 0.1111111111111111, - "#46039f" + 0.1, + "#c51b7d" ], [ - 0.2222222222222222, - "#7201a8" + 0.2, + "#de77ae" ], [ - 0.3333333333333333, - "#9c179e" + 0.3, + "#f1b6da" ], [ - 0.4444444444444444, - "#bd3786" + 0.4, + "#fde0ef" ], [ - 0.5555555555555556, - "#d8576b" + 0.5, + "#f7f7f7" ], [ - 0.6666666666666666, - "#ed7953" + 0.6, + "#e6f5d0" ], [ - 0.7777777777777778, - "#fb9f3a" + 0.7, + "#b8e186" ], [ - 0.8888888888888888, - "#fdca26" + 0.8, + "#7fbc41" ], [ - 1.0, - "#f0f921" + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" ] ], - "sequentialminus": [ + "sequential": [ [ - 0.0, + 0, "#0d0887" ], [ @@ -27191,106 +19712,125 @@ "#fdca26" ], [ - 1.0, + 1, "#f0f921" ] ], - "diverging": [ + "sequentialminus": [ [ 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" + "#0d0887" ], [ - 0.2, - "#de77ae" + 0.1111111111111111, + "#46039f" ], [ - 0.3, - "#f1b6da" + 0.2222222222222222, + "#7201a8" ], [ - 0.4, - "#fde0ef" + 0.3333333333333333, + "#9c179e" ], [ - 0.5, - "#f7f7f7" + 0.4444444444444444, + "#bd3786" ], [ - 0.6, - "#e6f5d0" + 0.5555555555555556, + "#d8576b" ], [ - 0.7, - "#b8e186" + 0.6666666666666666, + "#ed7953" ], [ - 0.8, - "#7fbc41" + 0.7777777777777778, + "#fb9f3a" ], [ - 0.9, - "#4d9221" + 0.8888888888888888, + "#fdca26" ], [ 1, - "#276419" + "#f0f921" ] ] }, - "xaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "automargin": true, - "zerolinewidth": 2 + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" }, - "yaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" }, - "zerolinecolor": "white", - "automargin": true, - "zerolinewidth": 2 + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", + "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 + "zerolinecolor": "white" }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", + "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 + "zerolinecolor": "white" }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", + "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white", - "gridwidth": 2 + "zerolinecolor": "white" } }, "shapedefaults": { @@ -27298,32 +19838,60 @@ "color": "#2a3f5f" } }, - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "geo": { - "bgcolor": "white", - "landcolor": "#E5ECF6", - "subunitcolor": "white", - "showland": true, - "showlakes": true, - "lakecolor": "white" + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } }, "title": { "x": 0.05 }, - "mapbox": { - "style": "light" + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 } } }, + "title": { + "text": "Up turn backtest date range trades overview for best performing algorithm" + }, + "width": 800, "xaxis": { "anchor": "y", "domain": [ - 0.0, - 1.0 + 0, + 1 ], "title": { "text": "Date" @@ -27332,39 +19900,34 @@ "yaxis": { "anchor": "x", "domain": [ - 0.0, - 1.0 + 0, + 1 ], "title": { "text": "Prices up turn" } - }, - "title": { - "text": "Up turn backtest date range trades overview for best performing algorithm" - }, - "height": 600, - "width": 800 - }, - "config": { - "plotlyServerURL": "https://plot.ly" + } } - }, - "text/html": "
" + } }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "from investing_algorithm_framework import pretty_print_backtest, create_trade_exit_markers_chart, create_trade_entry_markers_chart\n", + "from investing_algorithm_framework import pretty_print_backtest, \\\n", + " BacktestReportsEvaluation\n", "import pandas as pd\n", "\n", "# Set the weights for the evaluation criteria, where we prioritize the profit over the growth\n", + "evaluation = BacktestReportsEvaluation(reports)\n", "algorithm_name = evaluation.rank(weight_profit=0.7, weight_growth=0.3)\n", "print(f\"The winning algorithm is {algorithm_name}\")\n", "\n", "# Highlight the winning strategy by displaying the backtest result of the up turn date range\n", - "up_turn_report = evaluation.get_report(name=algorithm_name, backtest_date_range=up_turn_date_range)\n", + "up_turn_report = evaluation.get_report(\n", + " name=algorithm_name, backtest_date_range=up_turn_date_range\n", + ")\n", "pretty_print_backtest(up_turn_report)\n", "\n", "from plotly.subplots import make_subplots\n", @@ -27373,91 +19936,95 @@ "df_up_turn.set_index('Datetime', inplace=True)\n", "\n", "fig.add_trace(create_prices_graph(df_up_turn, name=\"Close price up turn\", color=\"blue\"), row=1, col=1)\n", - "fig.add_trace(create_trade_entry_markers_chart(df_up_turn, up_turn_report.trades), row=1, col=1)\n", - "fig.add_trace(create_trade_exit_markers_chart(df_up_turn, up_turn_report.trades), row=1, col=1)\n", + "fig.add_trace(\n", + " create_entry_graph(df_up_turn, up_turn_report.trades), row=1, col=1\n", + ")\n", + "fig.add_trace(\n", + " create_exit_graph(df_up_turn, up_turn_report.trades), row=1, col=1\n", + ")\n", "fig.update_layout(height=600, width=800, title_text=\"Up turn backtest date range trades overview for best performing algorithm\")\n", "fig.update_xaxes(title_text=\"Date\", row=1, col=1)\n", "fig.update_yaxes(title_text=\"Prices up turn\", row=1, col=1)\n", "fig.show()" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", - "source": [ - "## Step 6: Optimizing the winning strategy with different parameters" - ], "metadata": { "collapsed": false - } + }, + "source": [ + "## Step 6: Optimizing the winning strategy with different parameters" + ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92mdown_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 for a total of 4 algorithms.\u001B[0m\n" + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92mdown_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 for a total of 4 algorithms.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 875.64it/s]\n", - "Running backtest for algorithm with name primary_21_50: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:12<00:00, 170.01it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1153.55it/s]\n", - "Running backtest for algorithm with name primary_21_75: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:10<00:00, 202.24it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 778.24it/s]\n", - "Running backtest for algorithm with name primary_50_100: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:09<00:00, 234.25it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1085.76it/s]\n", - "Running backtest for algorithm with name primary_50_200: 100%|\u001B[32m██████████\u001B[0m| 2173/2173 [00:09<00:00, 231.95it/s]\n" + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 81.52it/s]\n", + "Running backtest for algorithm with name primary_21_50: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:19<00:00, 109.79it/s]\n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 195.99it/s]\n", + "Running backtest for algorithm with name primary_21_75: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:23<00:00, 93.20it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 61.48it/s]\n", + "Running backtest for algorithm with name primary_50_100: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:20<00:00, 104.58it/s]\n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 276.06it/s]\n", + "Running backtest for algorithm with name primary_50_200: 100%|\u001b[32m██████████\u001b[0m| 2173/2173 [00:19<00:00, 112.87it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92mup_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 for a total of 4 algorithms.\u001B[0m\n" + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92mup_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 for a total of 4 algorithms.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 978.04it/s]\n", - "Running backtest for algorithm with name primary_21_50: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:09<00:00, 202.39it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1133.75it/s]\n", - "Running backtest for algorithm with name primary_21_75: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:08<00:00, 218.84it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1178.51it/s]\n", - "Running backtest for algorithm with name primary_50_100: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:07<00:00, 249.54it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 960.23it/s]\n", - "Running backtest for algorithm with name primary_50_200: 100%|\u001B[32m██████████\u001B[0m| 1957/1957 [00:07<00:00, 264.58it/s]\n" + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 181.62it/s]\n", + "Running backtest for algorithm with name primary_21_50: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:22<00:00, 88.41it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 151.12it/s]\n", + "Running backtest for algorithm with name primary_21_75: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:21<00:00, 92.59it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 260.08it/s]\n", + "Running backtest for algorithm with name primary_50_100: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:17<00:00, 109.98it/s]\n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 230.50it/s]\n", + "Running backtest for algorithm with name primary_50_200: 100%|\u001b[32m██████████\u001b[0m| 1957/1957 [00:19<00:00, 102.87it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001B[93mRunning backtests for date range:\u001B[0m \u001B[92msideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 for a total of 4 algorithms.\u001B[0m\n" + "\u001b[93mRunning backtests for date range:\u001b[0m \u001b[92msideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 for a total of 4 algorithms.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1031.05it/s]\n", - "Running backtest for algorithm with name primary_21_50: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:12<00:00, 209.66it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1356.06it/s]\n", - "Running backtest for algorithm with name primary_21_75: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:11<00:00, 217.44it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 949.69it/s]\n", - "Running backtest for algorithm with name primary_50_100: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:11<00:00, 232.72it/s]\n", - "Preparing backtest market data: 100%|\u001B[32m██████████\u001B[0m| 2/2 [00:00<00:00, 1103.04it/s]\n", - "Running backtest for algorithm with name primary_50_200: 100%|\u001B[32m██████████\u001B[0m| 2569/2569 [00:10<00:00, 240.03it/s]" + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 150.94it/s]\n", + "Running backtest for algorithm with name primary_21_50: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:30<00:00, 85.42it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 361.66it/s]\n", + "Running backtest for algorithm with name primary_21_75: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:27<00:00, 93.56it/s] \n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 333.78it/s]\n", + "Running backtest for algorithm with name primary_50_100: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:23<00:00, 110.81it/s]\n", + "Preparing backtest market data: 100%|\u001b[32m██████████\u001b[0m| 2/2 [00:00<00:00, 113.52it/s]\n", + "Running backtest for algorithm with name primary_50_200: 100%|\u001b[32m██████████\u001b[0m| 2569/2569 [00:21<00:00, 118.72it/s]" ] }, { @@ -27465,145 +20032,151 @@ "output_type": "stream", "text": [ "\n", - " :%%%#+- .=*#%%% \u001B[92mBacktest reports evaluation\u001B[0m\n", - " *%%%%%%%+------=*%%%%%%%- \u001B[92m---------------------------\u001B[0m\n", - " *%%%%%%%%%%%%%%%%%%%%%%%- \u001B[93mNumber of reports:\u001B[0m \u001B[92m4 backtest reports\u001B[0m\n", - " .%%%%%%%%%%%%%%%%%%%%%%# \u001B[93mLargest overall profit:\u001B[0m\u001B[92m\u001B[0m\u001B[92m (Algorithm primary_21_50) 121.6183 EUR 12.1618% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001B[0m \n", - " #%%%####%%%%%%%%**#%%%+ \u001B[93mLargest overall growth:\u001B[0m\u001B[92m (Algorithm primary_21_50) 127.5655 EUR 12.7565% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001B[0m\n", - " .:-+*%%%%- \u001B[95m-+..#\u001B[0m%%%+.\u001B[95m+- +\u001B[0m%%%#*=-: \n", - " .:-=*%%%%. \u001B[95m+=\u001B[0m .%%# \u001B[95m-+.-\u001B[0m%%%%=-:.. \n", - " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \n", - " +%%%%%%%%%%%%%%%%%%%= \n", - " :++ .=#%%%%%%%%%%%%%*- \n", - " :++: :+%%%%%%#-. \n", - " :++: .%%%%%#= \n", - " :++: .#%%%%%#*= \n", - " :++- :%%%%%%%%%+= \n", - " .++- -%%%%%%%%%%%+= \n", - " .++- .%%%%%%%%%%%%%+= \n", - " .++- *%%%%%%%%%%%%%*+: \n", - " .++- %%%%%%%%%%%%%%#+= \n", - " =++........:::%%%%%%%%%%%%%%*+- \n", - " .=++++++++++**#%%%%%%%%%%%%%++. \n", + " :%%%#+- .=*#%%% \u001b[92mBacktest reports evaluation\u001b[0m\n", + " *%%%%%%%+------=*%%%%%%%- \u001b[92m---------------------------\u001b[0m\n", + " *%%%%%%%%%%%%%%%%%%%%%%%- \u001b[93mNumber of reports:\u001b[0m \u001b[92m4 backtest reports\u001b[0m\n", + " .%%%%%%%%%%%%%%%%%%%%%%# \u001b[93mLargest overall profit:\u001b[0m\u001b[92m\u001b[0m\u001b[92m (Algorithm primary_21_50) 119.9648 EUR 11.9965% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001b[0m\n", + " #%%%####%%%%%%%%**#%%%+ \u001b[93mLargest overall growth:\u001b[0m\u001b[92m (Algorithm primary_21_50) 125.2748 EUR 12.5275% (up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00)\u001b[0m\n", + " .:-+*%%%%- \u001b[95m-+..#\u001b[0m%%%+.\u001b[95m+- +\u001b[0m%%%#*=-:\n", + " .:-=*%%%%. \u001b[95m+=\u001b[0m .%%# \u001b[95m-+.-\u001b[0m%%%%=-:..\n", + " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-:\n", + " +%%%%%%%%%%%%%%%%%%%=\n", + " :++ .=#%%%%%%%%%%%%%*-\n", + " :++: :+%%%%%%#-.\n", + " :++: .%%%%%#=\n", + " :++: .#%%%%%#*=\n", + " :++- :%%%%%%%%%+=\n", + " .++- -%%%%%%%%%%%+=\n", + " .++- .%%%%%%%%%%%%%+=\n", + " .++- *%%%%%%%%%%%%%*+:\n", + " .++- %%%%%%%%%%%%%%#+=\n", + " =++........:::%%%%%%%%%%%%%%*+-\n", + " .=++++++++++**#%%%%%%%%%%%%%++.\n", " \n", - "\u001B[93mAll profits ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬───────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Profit │ Profit percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ 121.6183 EUR │ 12.1618% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1127.57 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ 120.0602 EUR │ 12.0060% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1126.31 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ 84.0464 EUR │ 8.4046% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1085.4 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ 54.1972 EUR │ 5.4197% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1055.91 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴───────────────────────────────────────────────────┴───────────────╯\n", - "\u001B[93mAll growths ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬───────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Growth │ Growth percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ 127.5655 EUR │ 12.7565% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1127.57 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ 126.3098 EUR │ 12.6310% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1126.31 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ 85.3999 EUR │ 8.5400% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1085.4 │\n", - "├──────────────────┼──────────────┼─────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ 55.9132 EUR │ 5.5913% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1055.91 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴───────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mPrice noise\u001b[0m\n", + "\n", + "\u001b[93mAll profits ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬───────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Profit │ Profit percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ 119.9648 EUR │ 11.9965% │ 26% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1125.27 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ 118.5247 EUR │ 11.8525% │ 29% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1124.1 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ 79.6898 EUR │ 7.9690% │ 67% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1079.69 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ 9.7453 EUR │ 0.9745% │ 40% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1011.36 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴───────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mAll growths ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬───────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Growth │ Growth percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ 125.2748 EUR │ 12.5275% │ 26% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1125.27 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ 124.1047 EUR │ 12.4105% │ 29% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1124.1 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ 79.6898 EUR │ 7.9690% │ 67% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1079.69 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼───────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ 11.3623 EUR │ 1.1362% │ 40% │ up_turn 2022-12-20 00:00:00 - 2023-06-01 00:00:00 │ 1011.36 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴───────────────────────────────────────────────────┴───────────────╯\n", "The winning configuration for the up turn backtest date range is primary_21_50\n", "\n", - " :%%%#+- .=*#%%% \u001B[92mBacktest reports evaluation\u001B[0m\n", - " *%%%%%%%+------=*%%%%%%%- \u001B[92m---------------------------\u001B[0m\n", - " *%%%%%%%%%%%%%%%%%%%%%%%- \u001B[93mNumber of reports:\u001B[0m \u001B[92m4 backtest reports\u001B[0m\n", - " .%%%%%%%%%%%%%%%%%%%%%%# \u001B[93mLargest overall profit:\u001B[0m\u001B[92m\u001B[0m\u001B[92m (Algorithm primary_21_50) -8.5178 EUR -0.8518% (sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00)\u001B[0m \n", - " #%%%####%%%%%%%%**#%%%+ \u001B[93mLargest overall growth:\u001B[0m\u001B[92m (Algorithm primary_21_50) -2.3874 EUR -0.2387% (sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00)\u001B[0m\n", - " .:-+*%%%%- \u001B[95m-+..#\u001B[0m%%%+.\u001B[95m+- +\u001B[0m%%%#*=-: \n", - " .:-=*%%%%. \u001B[95m+=\u001B[0m .%%# \u001B[95m-+.-\u001B[0m%%%%=-:.. \n", - " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \n", - " +%%%%%%%%%%%%%%%%%%%= \n", - " :++ .=#%%%%%%%%%%%%%*- \n", - " :++: :+%%%%%%#-. \n", - " :++: .%%%%%#= \n", - " :++: .#%%%%%#*= \n", - " :++- :%%%%%%%%%+= \n", - " .++- -%%%%%%%%%%%+= \n", - " .++- .%%%%%%%%%%%%%+= \n", - " .++- *%%%%%%%%%%%%%*+: \n", - " .++- %%%%%%%%%%%%%%#+= \n", - " =++........:::%%%%%%%%%%%%%%*+- \n", - " .=++++++++++**#%%%%%%%%%%%%%++. \n", + " :%%%#+- .=*#%%% \u001b[92mBacktest reports evaluation\u001b[0m\n", + " *%%%%%%%+------=*%%%%%%%- \u001b[92m---------------------------\u001b[0m\n", + " *%%%%%%%%%%%%%%%%%%%%%%%- \u001b[93mNumber of reports:\u001b[0m \u001b[92m4 backtest reports\u001b[0m\n", + " .%%%%%%%%%%%%%%%%%%%%%%# \u001b[93mLargest overall profit:\u001b[0m\u001b[92m\u001b[0m\u001b[92m (Algorithm primary_21_50) -8.2249 EUR -0.8225% (sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00)\u001b[0m\n", + " #%%%####%%%%%%%%**#%%%+ \u001b[93mLargest overall growth:\u001b[0m\u001b[92m (Algorithm primary_21_50) -2.0557 EUR -0.2056% (sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00)\u001b[0m\n", + " .:-+*%%%%- \u001b[95m-+..#\u001b[0m%%%+.\u001b[95m+- +\u001b[0m%%%#*=-:\n", + " .:-=*%%%%. \u001b[95m+=\u001b[0m .%%# \u001b[95m-+.-\u001b[0m%%%%=-:..\n", + " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-:\n", + " +%%%%%%%%%%%%%%%%%%%=\n", + " :++ .=#%%%%%%%%%%%%%*-\n", + " :++: :+%%%%%%#-.\n", + " :++: .%%%%%#=\n", + " :++: .#%%%%%#*=\n", + " :++- :%%%%%%%%%+=\n", + " .++- -%%%%%%%%%%%+=\n", + " .++- .%%%%%%%%%%%%%+=\n", + " .++- *%%%%%%%%%%%%%*+:\n", + " .++- %%%%%%%%%%%%%%#+=\n", + " =++........:::%%%%%%%%%%%%%%*+-\n", + " .=++++++++++**#%%%%%%%%%%%%%++.\n", " \n", - "\u001B[93mAll profits ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Profit │ Profit percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ -8.5178 EUR │ -0.8518% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.613 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ -12.4677 EUR │ -1.2468% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 993.279 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ -27.2777 EUR │ -2.7278% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 975.484 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ -42.4574 EUR │ -4.2457% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 961.348 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴────────────────────────────────────────────────────┴───────────────╯\n", - "\u001B[93mAll growths ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Growth │ Growth percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ -2.3874 EUR │ -0.2387% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.613 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ -6.7215 EUR │ -0.6722% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 993.279 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ -24.5160 EUR │ -2.4516% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 975.484 │\n", - "├──────────────────┼──────────────┼─────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ -38.6522 EUR │ -3.8652% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 961.348 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴────────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mPrice noise\u001b[0m\n", + "\n", + "\u001b[93mAll profits ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Profit │ Profit percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ -8.2249 EUR │ -0.8225% │ 32% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.944 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ -10.4957 EUR │ -1.0496% │ 33% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 995.324 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ -32.8569 EUR │ -3.2857% │ 50% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 969.977 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ -47.5249 EUR │ -4.7525% │ 14% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 952.475 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴────────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mAll growths ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Growth │ Growth percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ -2.0557 EUR │ -0.2056% │ 32% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 997.944 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ -4.6763 EUR │ -0.4676% │ 33% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 995.324 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ -30.0231 EUR │ -3.0023% │ 50% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 969.977 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ -47.5249 EUR │ -4.7525% │ 14% │ sideways 2022-06-10 00:00:00 - 2023-01-10 00:00:00 │ 952.475 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴────────────────────────────────────────────────────┴───────────────╯\n", "The winning configuration for the side ways backtest date range is None\n", "\n", - " :%%%#+- .=*#%%% \u001B[92mBacktest reports evaluation\u001B[0m\n", - " *%%%%%%%+------=*%%%%%%%- \u001B[92m---------------------------\u001B[0m\n", - " *%%%%%%%%%%%%%%%%%%%%%%%- \u001B[93mNumber of reports:\u001B[0m \u001B[92m4 backtest reports\u001B[0m\n", - " .%%%%%%%%%%%%%%%%%%%%%%# \u001B[93mLargest overall profit:\u001B[0m\u001B[92m\u001B[0m\u001B[92m (Algorithm primary_50_100) -40.0021 EUR -4.0002% (down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00)\u001B[0m \n", - " #%%%####%%%%%%%%**#%%%+ \u001B[93mLargest overall growth:\u001B[0m\u001B[92m (Algorithm primary_50_100) -40.0021 EUR -4.0002% (down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00)\u001B[0m\n", - " .:-+*%%%%- \u001B[95m-+..#\u001B[0m%%%+.\u001B[95m+- +\u001B[0m%%%#*=-: \n", - " .:-=*%%%%. \u001B[95m+=\u001B[0m .%%# \u001B[95m-+.-\u001B[0m%%%%=-:.. \n", - " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: \n", - " +%%%%%%%%%%%%%%%%%%%= \n", - " :++ .=#%%%%%%%%%%%%%*- \n", - " :++: :+%%%%%%#-. \n", - " :++: .%%%%%#= \n", - " :++: .#%%%%%#*= \n", - " :++- :%%%%%%%%%+= \n", - " .++- -%%%%%%%%%%%+= \n", - " .++- .%%%%%%%%%%%%%+= \n", - " .++- *%%%%%%%%%%%%%*+: \n", - " .++- %%%%%%%%%%%%%%#+= \n", - " =++........:::%%%%%%%%%%%%%%*+- \n", - " .=++++++++++**#%%%%%%%%%%%%%++. \n", + " :%%%#+- .=*#%%% \u001b[92mBacktest reports evaluation\u001b[0m\n", + " *%%%%%%%+------=*%%%%%%%- \u001b[92m---------------------------\u001b[0m\n", + " *%%%%%%%%%%%%%%%%%%%%%%%- \u001b[93mNumber of reports:\u001b[0m \u001b[92m4 backtest reports\u001b[0m\n", + " .%%%%%%%%%%%%%%%%%%%%%%# \u001b[93mLargest overall profit:\u001b[0m\u001b[92m\u001b[0m\u001b[92m (Algorithm primary_50_200) -33.8225 EUR -3.3823% (down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00)\u001b[0m\n", + " #%%%####%%%%%%%%**#%%%+ \u001b[93mLargest overall growth:\u001b[0m\u001b[92m (Algorithm primary_50_200) -33.8226 EUR -3.3823% (down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00)\u001b[0m\n", + " .:-+*%%%%- \u001b[95m-+..#\u001b[0m%%%+.\u001b[95m+- +\u001b[0m%%%#*=-:\n", + " .:-=*%%%%. \u001b[95m+=\u001b[0m .%%# \u001b[95m-+.-\u001b[0m%%%%=-:..\n", + " .:=+#%%%%%*###%%%%#*+#%%%%%%*+-:\n", + " +%%%%%%%%%%%%%%%%%%%=\n", + " :++ .=#%%%%%%%%%%%%%*-\n", + " :++: :+%%%%%%#-.\n", + " :++: .%%%%%#=\n", + " :++: .#%%%%%#*=\n", + " :++- :%%%%%%%%%+=\n", + " .++- -%%%%%%%%%%%+=\n", + " .++- .%%%%%%%%%%%%%+=\n", + " .++- *%%%%%%%%%%%%%*+:\n", + " .++- %%%%%%%%%%%%%%#+=\n", + " =++........:::%%%%%%%%%%%%%%*+-\n", + " .=++++++++++**#%%%%%%%%%%%%%++.\n", " \n", - "\u001B[93mAll profits ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Profit │ Profit percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ -40.0021 EUR │ -4.0002% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 959.998 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ -57.1643 EUR │ -5.7164% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 942.836 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ -65.6861 EUR │ -6.5686% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 934.314 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ -74.4635 EUR │ -7.4464% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 925.537 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", - "\u001B[93mAll growths ordered\u001B[0m\n", - "╭──────────────────┬──────────────┬─────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", - "│ Algorithm name │ Growth │ Growth percentage │ Date range │ Total value │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_100 │ -40.0021 EUR │ -4.0002% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 959.998 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_50 │ -57.1643 EUR │ -5.7164% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 942.836 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_50_200 │ -65.6861 EUR │ -6.5686% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 934.314 │\n", - "├──────────────────┼──────────────┼─────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", - "│ primary_21_75 │ -74.4635 EUR │ -7.4463% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 925.537 │\n", - "╰──────────────────┴──────────────┴─────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mPrice noise\u001b[0m\n", + "\n", + "\u001b[93mAll profits ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Profit │ Profit percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ -33.8225 EUR │ -3.3823% │ 20% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 966.177 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ -68.4083 EUR │ -6.8408% │ 18% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 931.592 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ -76.2687 EUR │ -7.6269% │ 16% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 923.731 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ -95.0346 EUR │ -9.5035% │ 19% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 904.965 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", + "\u001b[93mAll growths ordered\u001b[0m\n", + "╭──────────────────┬──────────────┬─────────────────────┬──────────────────────────────┬─────────────────────────────────────────────────────┬───────────────╮\n", + "│ Algorithm name │ Growth │ Growth percentage │ Percentage positive trades │ Date range │ Total value │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_200 │ -33.8226 EUR │ -3.3823% │ 20% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 966.177 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_50_100 │ -68.4082 EUR │ -6.8408% │ 18% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 931.592 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_75 │ -76.2687 EUR │ -7.6269% │ 16% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 923.731 │\n", + "├──────────────────┼──────────────┼─────────────────────┼──────────────────────────────┼─────────────────────────────────────────────────────┼───────────────┤\n", + "│ primary_21_50 │ -95.0346 EUR │ -9.5035% │ 19% │ down_turn 2021-12-21 00:00:00 - 2022-06-20 00:00:00 │ 904.965 │\n", + "╰──────────────────┴──────────────┴─────────────────────┴──────────────────────────────┴─────────────────────────────────────────────────────┴───────────────╯\n", "The winning configuration for the down turn backtest date range is None\n" ] }, @@ -27647,7 +20220,7 @@ "algorithms = []\n", "\n", "for configuration in configurations:\n", - " algorithm = create_primary_algorithm(**configuration)\n", + " algorithm = create_algorithm(**configuration)\n", " algorithms.append(algorithm)\n", "\n", "reports = app.run_backtests(algorithms=algorithms, date_ranges=[down_turn_date_range, up_turn_date_range, sideways_date_range])\n", @@ -27663,49 +20236,46 @@ "pretty_print_backtest_reports_evaluation(evaluation, backtest_date_range=down_turn_date_range)\n", "winning_configuration = evaluation.rank(weight_profit=0.7, weight_growth=0.3, backtest_date_range=down_turn_date_range)\n", "print(f\"The winning configuration for the down turn backtest date range is {winning_configuration}\")" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", - "source": [ - "## Conclusion" - ], "metadata": { "collapsed": false - } + }, + "source": [ + "## Conclusion" + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ "In this guide we have demonstrated how to leverage the backtests functionality within the Investing Algorithm Framework to conduct A/B Testing effectively. We have compared the performance of multiple models, such as challenger versus champion, and experimented with diverse backtest date ranges and parameter configurations. This process of comparing the challenger model to the production model, or champion, falls under the umbrella of model validation or model evaluation. It entails meticulously assessing the challenger model's performance in relation to the established production model. The Experiment functionality proves invaluable for fine-tuning strategy parameters and comparing different strategies, whether it's testing a new challenger model against the existing production model (champion), or exploring variations in configuration.\n", "\n", "Also, when selected an algorithm we showed how to test different configurations of the winning algorithm to optimize the strategy. This process is crucial to ensure that the algorithm is performing at its best and to maximize the profit. By following these steps, you can effectively compare different algorithms and configurations to identify the best performing strategy for your trading needs." - ], - "metadata": { - "collapsed": false - } + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.10.16" } }, "nbformat": 4, diff --git a/examples/bitvavo_trading_bot/bitvavo.py b/examples/bitvavo_trading_bot.py similarity index 85% rename from examples/bitvavo_trading_bot/bitvavo.py rename to examples/bitvavo_trading_bot.py index a3a69852..af0ed44d 100644 --- a/examples/bitvavo_trading_bot/bitvavo.py +++ b/examples/bitvavo_trading_bot.py @@ -1,6 +1,9 @@ +from dotenv import load_dotenv +import logging.config + from investing_algorithm_framework import MarketCredential, TimeUnit, \ CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource, TradingStrategy, \ - create_app, PortfolioConfiguration, Algorithm + create_app, PortfolioConfiguration, Algorithm, DEFAULT_LOGGING_CONFIG """ Bitvavo trading bot example with market data sources of bitvavo. @@ -11,11 +14,14 @@ account on bitvavo. """ -# Define your market credential for bitvavo +logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) + +# Load the environment variables from the .env file +load_dotenv() + +# Define your market credential for bitvavo, keys are read from .env file bitvavo_market_credential = MarketCredential( market="bitvavo", - api_key="your_api_key", - secret_key="your_secret_key" ) # Define your market data sources for coinbase bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( @@ -48,8 +54,6 @@ def apply_strategy(self, algorithm, market_data): # Create an app and add the market data sources and market credentials to it app = create_app() app.add_market_credential(bitvavo_market_credential) -app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h) -app.add_market_data_source(bitvavo_btc_eur_ticker) # Register your algorithm and portfolio configuration to the app app.add_algorithm(algorithm) diff --git a/examples/bitvavo_trading_bot/__init__.py b/examples/bitvavo_trading_bot/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/coinbase_trading_bot/coinbase.py b/examples/coinbase_trading_bot.py similarity index 72% rename from examples/coinbase_trading_bot/coinbase.py rename to examples/coinbase_trading_bot.py index 2c2b1331..66064625 100644 --- a/examples/coinbase_trading_bot/coinbase.py +++ b/examples/coinbase_trading_bot.py @@ -1,22 +1,25 @@ -import os - +from dotenv import load_dotenv +import logging.config from investing_algorithm_framework import MarketCredential, TimeUnit, \ CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource, TradingStrategy, \ - create_app, PortfolioConfiguration, Algorithm, SYMBOLS, RESOURCE_DIRECTORY - + create_app, PortfolioConfiguration, Algorithm, DEFAULT_LOGGING_CONFIG """ Coinbase market data sources example. Coinbase requires you to have an API key and secret key to access their market data. You can create them here: https://www.coinbase.com/settings/api -You need to add a market credential to the app, and then add market -data sources to the app. You can then use the market data +You need to add a market credential to the app, and then add market +data sources to the app. You can then use the market data sources in your trading strategy. """ -# Define your market credential for coinbase + +logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) + +# Load the environment variables from the .env file +load_dotenv() + +# Define your market credential for coinbase, keys are read from .env file coinbase_market_credential = MarketCredential( - api_key="", - secret_key="", market="coinbase", ) # Define your market data sources for coinbase @@ -24,7 +27,7 @@ identifier="BTC/EUR-ohlcv", market="coinbase", symbol="BTC/EUR", - timeframe="2h", + time_frame="2h", window_size=200 ) coinbase_btc_eur_ticker = CCXTTickerMarketDataSource( @@ -42,18 +45,11 @@ class CoinBaseTradingStrategy(TradingStrategy): def apply_strategy(self, algorithm, market_data): pass - -config = { - SYMBOLS: ["BTC/EUR"], - RESOURCE_DIRECTORY: os.path.join(os.path.dirname(__file__), "resources") -} algorithm = Algorithm() algorithm.add_strategy(CoinBaseTradingStrategy) -app = create_app(config=config) +app = create_app() app.add_algorithm(algorithm) app.add_market_credential(coinbase_market_credential) -app.add_market_data_source(coinbase_btc_eur_ohlcv_2h) -app.add_market_data_source(coinbase_btc_eur_ticker) app.add_portfolio_configuration(PortfolioConfiguration( initial_balance=1000, trading_symbol="EUR", diff --git a/examples/coinbase_trading_bot/__init__.py b/examples/coinbase_trading_bot/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/crossover_moving_average_trading_bot/__init__.py b/examples/crossover_moving_average_trading_bot/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/crossover_moving_average_trading_bot/algorithm/__init__.py b/examples/crossover_moving_average_trading_bot/algorithm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/crossover_moving_average_trading_bot/algorithm/algorithm.py b/examples/crossover_moving_average_trading_bot/algorithm/algorithm.py deleted file mode 100644 index 6c9caa3c..00000000 --- a/examples/crossover_moving_average_trading_bot/algorithm/algorithm.py +++ /dev/null @@ -1,5 +0,0 @@ -from investing_algorithm_framework import Algorithm -from .strategy import CrossOverStrategy - -algorithm = Algorithm() -algorithm.add_strategy(CrossOverStrategy) diff --git a/examples/crossover_moving_average_trading_bot/algorithm/data_sources.py b/examples/crossover_moving_average_trading_bot/algorithm/data_sources.py deleted file mode 100644 index 04243040..00000000 --- a/examples/crossover_moving_average_trading_bot/algorithm/data_sources.py +++ /dev/null @@ -1,30 +0,0 @@ -from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \ - CCXTTickerMarketDataSource - - -bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( - identifier="BTC/EUR-ohlcv", - market="BINANCE", - symbol="BTC/EUR", - timeframe="2h", - window_size=200 -) -bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( - identifier="DOT/EUR-ohlcv", - market="BINANCE", - symbol="DOT/EUR", - timeframe="2h", - window_size=200 -) -bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( - identifier="DOT/EUR-ticker", - market="BINANCE", - symbol="DOT/EUR", - backtest_timeframe="2h", -) -bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( - identifier="BTC/EUR-ticker", - market="BINANCE", - symbol="BTC/EUR", - backtest_timeframe="2h", -) diff --git a/examples/crossover_moving_average_trading_bot/algorithm/strategy.py b/examples/crossover_moving_average_trading_bot/algorithm/strategy.py deleted file mode 100644 index d0e1f1a4..00000000 --- a/examples/crossover_moving_average_trading_bot/algorithm/strategy.py +++ /dev/null @@ -1,96 +0,0 @@ -import tulipy as ti - -from investing_algorithm_framework import TimeUnit, TradingStrategy, \ - Algorithm, OrderSide -from .data_sources import bitvavo_btc_eur_ohlcv_2h, bitvavo_btc_eur_ticker, \ - bitvavo_dot_eur_ticker, bitvavo_dot_eur_ohlcv_2h - - -""" -This strategy is based on the golden cross strategy. It will buy when the -fast moving average crosses the slow moving average from below. It will sell -when the fast moving average crosses the slow moving average from above. - -The strategy will also check if the fast moving average is above the trend -moving average. If it is not above the trend moving average it will not buy. - -It uses tulipy indicators to calculate the metrics. You need to -install this library in your environment to run this strategy. -You can find instructions on how to install tulipy here: -https://tulipindicators.org/ or go directly to the pypi page: -https://pypi.org/project/tulipy/ -""" - - -# Define market data sources -def is_below_trend(fast_series, slow_series): - return fast_series[-1] < slow_series[-1] - - -def is_above_trend(fast_series, slow_series): - return fast_series[-1] > slow_series[-1] - - -def is_crossover(fast, slow): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return fast[-2] <= slow[-2] and fast[-1] > slow[-1] - - -def is_crossunder(fast, slow): - """ - Expect df to have columns: Date, ma_, ma_. - With the given date time it will check if the ma_ is a - crossover with the ma_ - """ - return fast[-2] >= slow[-2] and fast[-1] < slow[-1] - - -class CrossOverStrategy(TradingStrategy): - time_unit = TimeUnit.HOUR - interval = 2 - market_data_sources = [ - bitvavo_dot_eur_ticker, - bitvavo_dot_eur_ohlcv_2h, - bitvavo_btc_eur_ticker, - bitvavo_btc_eur_ohlcv_2h - ] - symbols = ["BTC/EUR", "DOT/EUR"] - - def apply_strategy(self, algorithm: Algorithm, market_data): - - for symbol in self.symbols: - target_symbol = symbol.split('/')[0] - - if algorithm.has_open_orders(target_symbol): - continue - - df = market_data[f"{symbol}-ohlcv"] - ticker_data = market_data[f"{symbol}-ticker"] - fast = ti.sma(df['Close'].to_numpy(), 9) - slow = ti.sma(df['Close'].to_numpy(), 50) - trend = ti.sma(df['Close'].to_numpy(), 100) - price = ticker_data['bid'] - - if not algorithm.has_position(target_symbol) \ - and is_crossover(fast, slow)\ - and not is_above_trend(fast, trend): - algorithm.create_limit_order( - target_symbol=target_symbol, - order_side=OrderSide.BUY, - price=price, - percentage_of_portfolio=25, - precision=4, - ) - - if algorithm.has_position(target_symbol) \ - and is_below_trend(fast, slow): - open_trades = algorithm.get_open_trades( - target_symbol=target_symbol - ) - - for trade in open_trades: - algorithm.close_trade(trade) diff --git a/examples/crossover_moving_average_trading_bot/app.py b/examples/crossover_moving_average_trading_bot/app.py deleted file mode 100644 index 758ed40b..00000000 --- a/examples/crossover_moving_average_trading_bot/app.py +++ /dev/null @@ -1,10 +0,0 @@ -import pathlib - -from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY, \ - SYMBOLS - -config = { - SYMBOLS: ["BTC/EUR"], - RESOURCE_DIRECTORY: pathlib.Path(__file__).parent.resolve() -} -app = create_app(config=config) diff --git a/examples/crossover_moving_average_trading_bot/backtesting.py b/examples/crossover_moving_average_trading_bot/backtesting.py deleted file mode 100644 index 9e34b6ce..00000000 --- a/examples/crossover_moving_average_trading_bot/backtesting.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import datetime, timedelta - -from algorithm.algorithm import algorithm -from algorithm.data_sources import bitvavo_btc_eur_ohlcv_2h, \ - bitvavo_dot_eur_ohlcv_2h, bitvavo_dot_eur_ticker, bitvavo_btc_eur_ticker -from app import app -from investing_algorithm_framework import PortfolioConfiguration, \ - pretty_print_backtest, BacktestDateRange - -app.add_algorithm(algorithm) -app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h) -app.add_market_data_source(bitvavo_dot_eur_ohlcv_2h) -app.add_market_data_source(bitvavo_btc_eur_ticker) -app.add_market_data_source(bitvavo_dot_eur_ticker) - - -# Add a portfolio configuration of 400 euro initial balance -app.add_portfolio_configuration( - PortfolioConfiguration( - market="BINANCE", - trading_symbol="EUR", - initial_balance=400, - ) -) - -if __name__ == "__main__": - end_date = datetime(2023, 12, 2) - start_date = end_date - timedelta(days=100) - date_range = BacktestDateRange( - start_date=start_date, - end_date=end_date - ) - backtest_report = app.run_backtest( - algorithm=algorithm, - backtest_date_range=date_range, - pending_order_check_interval="2h", - ) - pretty_print_backtest(backtest_report) diff --git a/examples/crossover_moving_average_trading_bot/production.py b/examples/crossover_moving_average_trading_bot/production.py deleted file mode 100644 index 8319d2e8..00000000 --- a/examples/crossover_moving_average_trading_bot/production.py +++ /dev/null @@ -1,13 +0,0 @@ -from app import app -from investing_algorithm_framework import MarketCredential - -# Configure your market credentials here -bitvavo_market_credential = MarketCredential( - api_key="", - secret_key="", - market="BITVAVO" -) -app.add_market_credential(bitvavo_market_credential) - -if __name__ == "__main__": - app.run() diff --git a/examples/test.py b/examples/test.py deleted file mode 100644 index 85769738..00000000 --- a/examples/test.py +++ /dev/null @@ -1,79 +0,0 @@ -import requests - -if __name__ == "__main__": - # # Endpoint for Treasury Yield Curve Rates API - # url = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/avg_interest_rates?filter=record_date:gte:2024-01-01" - # - # # Make a GET request to the API - # response = requests.get(url) - # - # if response.status_code == 200: - # # Extract risk-free rate from API response (e.g., 10-year Treasury yield) - # treasury_yield_data = response.json() - # entries = treasury_yield_data["data"] - # for entry in entries: - # print(entry) - # print(entries[-1]) - # print(entries[-1]["avg_interest_rate_amt"]) - # # print(treasury_yield_data) - # # ten_year_yield = treasury_yield_data["data"][0]["value"] - # # risk_free_rate = ten_year_yield / 100 # Convert percentage to decimal - # # print("10-Year Treasury Yield (Risk-Free Rate):", risk_free_rate) - # else: - # print("Failed to retrieve Treasury yield data. Status code:", response.status_code) - - from datetime import datetime, timedelta - from investing_algorithm_framework import CCXTOHLCVMarketDataSource - from pandas import to_datetime - - DATA_RECORDING_START_DATE = datetime(year=2024, month=1, day=1) - DATETIME_FORMAT = "%Y-%m-%d-%H-%M" - data_resources = ["BTC/EUR", "DOT/EUR", "ETH/EUR", "ADA/EUR"] - data_symbols = { - "BTC/EUR": { - "intervals": ["15m", "2h", "1d"], - "market": "bitvavo" - }, - "DOT/EUR": { - "intervals": ["15m", "2h", "1d"], - "market": "bitvavo" - }, - "ETH/EUR": { - "intervals": ["15m", "2h", "1d"], - "market": "bitvavo" - }, - "ADA/EUR": { - "intervals": ["15m", "2h", "1d"], - "market": "bitvavo" - } - } - - # Loop over all data sources and their intervals and download - # all data. After downloading each data source, write everything to - # the fabric lakehous - for symbol in data_symbols: - config = data_symbols[symbol] - - for interval in config["intervals"]: - data_source = CCXTOHLCVMarketDataSource( - identifier="BTC-ohlcv", - market=config["market"], - symbol=symbol, - timeframe=interval, - ) - last_date = DATA_RECORDING_START_DATE - - data = data_source.get_data(start_date=last_date, - end_date=datetime.utcnow()) - pandas_df = data.to_pandas() - - # if len(pandas_df) > 0: - # pandas_df['Datetime'] = to_datetime(pandas_df['Datetime']) - # start_date = pandas_df.head(1)["Datetime"].iloc[0].strftime( - # DATETIME_FORMAT) - # end_date = pandas_df.tail(1)["Datetime"].iloc[0].strftime( - # DATETIME_FORMAT) - # formatted_symbol = symbol.replace('/', '_') - # spark_df = spark.createDataFrame(pandas_df) - # spark_df.write.mode("overwrite").parquet( - # f'Files/market_data/{formatted_symbol}_{interval}_{start_date}_{end_date}.parquet') \ No newline at end of file diff --git a/investing_algorithm_framework/__init__.py b/investing_algorithm_framework/__init__.py index 65aa296e..57a00fa8 100644 --- a/investing_algorithm_framework/__init__.py +++ b/investing_algorithm_framework/__init__.py @@ -12,7 +12,7 @@ RESERVED_BALANCES, APP_MODE, AppMode, DATETIME_FORMAT, \ load_backtest_report, BacktestDateRange, convert_polars_to_pandas, \ DateRange, get_backtest_report, DEFAULT_LOGGING_CONFIG, \ - BacktestReport + BacktestReport, TradeStatus, MarketDataType from investing_algorithm_framework.infrastructure import \ CCXTOrderBookMarketDataSource, CCXTOHLCVMarketDataSource, \ CCXTTickerMarketDataSource, CSVOHLCVMarketDataSource, \ @@ -90,5 +90,7 @@ "get_backtest_report", "AzureBlobStorageStateHandler", "DEFAULT_LOGGING_CONFIG", - "BacktestReport" + "BacktestReport", + "TradeStatus", + "MarketDataType" ] diff --git a/investing_algorithm_framework/app/algorithm.py b/investing_algorithm_framework/app/algorithm.py index 1b34adae..c8349d2b 100644 --- a/investing_algorithm_framework/app/algorithm.py +++ b/investing_algorithm_framework/app/algorithm.py @@ -4,7 +4,7 @@ import re from investing_algorithm_framework.domain import OrderStatus, \ - Position, Order, Portfolio, OrderType, OrderSide, \ + Position, Order, Portfolio, OrderType, OrderSide, TradeStatus, \ BACKTESTING_FLAG, BACKTESTING_INDEX_DATETIME, MarketService, TimeUnit, \ OperationalException, random_string, RoundingService, Trade from investing_algorithm_framework.services import MarketCredentialService, \ @@ -61,7 +61,7 @@ def __init__( self.market_service: MarketService self.configuration_service: ConfigurationService self.portfolio_configuration_service: PortfolioConfigurationService - self.strategy_orchestrator_service: StrategyOrchestratorService + self.strategy_orchestrator_service: StrategyOrchestratorService = None self._data_sources = {} self._strategies = [] self._market_credential_service: MarketCredentialService @@ -164,6 +164,19 @@ def start(self, number_of_iterations: int = None): number_of_iterations=number_of_iterations ) + def stop(self) -> None: + """ + Function to stop the algorithm. This function will stop the + algorithm by stopping all jobs in the strategy orchestrator + service. + + Returns: + None + """ + + if self.strategy_orchestrator_service is not None: + self.strategy_orchestrator_service.stop() + @property def name(self): return self._name @@ -186,6 +199,14 @@ def config(self): """ return self.configuration_service.get_config() + def get_config(self): + """ + Function to get a config instance. This allows users when + having access to the algorithm instance also to read the + configs of the app. + """ + return self.configuration_service.get_config() + @property def description(self): """ @@ -329,7 +350,7 @@ def create_limit_order( order and execute it if the execute parameter is set to True. If the validate parameter is set to True, the order will be validated - Parameters: + Args: target_symbol: The symbol of the asset to trade price: The price of the asset order_side: The side of the order @@ -490,7 +511,7 @@ def get_portfolio(self, market=None) -> Portfolio: """ if market is None: - return self.portfolio_service.find({}) + return self.portfolio_service.get_all()[0] return self.portfolio_service.find({{"market": market}}) @@ -511,7 +532,7 @@ def get_unallocated(self, market=None) -> float: If the market parameter is specified, the unallocated balance of the specified market will be returned. - Parameters: + Args: market: The market of the portfolio Returns: @@ -521,7 +542,7 @@ def get_unallocated(self, market=None) -> float: if market: portfolio = self.portfolio_service.find({{"market": market}}) else: - portfolio = self.portfolio_service.find({}) + portfolio = self.portfolio_service.get_all()[0] trading_symbol = portfolio.trading_symbol return self.position_service.find( @@ -553,6 +574,19 @@ def get_order( order_side=None, order_type=None ) -> Order: + """ + Function to retrieve an order. + + Exception is thrown when no param has been provided. + + Args: + reference_id [optional] (int): id given by the external + market or exchange. + market [optional] (str): the market that the order was + executed on. + target_symbol [optional] (str): the symbol of the asset + that the order was executed + """ query_params = {} if reference_id: @@ -577,6 +611,11 @@ def get_order( ) query_params["position"] = [position.id for position in positions] + if not query_params: + raise OperationalException( + "No parameters provided to get order." + ) + return self.order_service.find(query_params) def get_orders( @@ -1183,25 +1222,95 @@ def has_open_orders( query_params["status"] = OrderStatus.OPEN.value return self.order_service.exists(query_params) - def check_pending_orders(self): + def get_trade( + self, + target_symbol=None, + trading_symbol=None, + market=None, + portfolio=None, + status=None, + order_id=None + ) -> List[Trade]: """ - Function to check pending orders + Function to get all trades. This function will return all trades + that match the specified query parameters. If the market parameter + is specified, the trades with the specified market will be returned. + + Args: + market: The market of the asset + portfolio: The portfolio of the asset + status: The status of the trade + order_id: The order id of the trade + target_symbol: The symbol of the asset + trading_symbol: The trading symbol of the asset + + Returns: + List[Trade]: A list of trades that match the query parameters """ - self.order_service.check_pending_orders() + query_params = {} + + if market is not None: + query_params["market"] = market + + if portfolio is not None: + query_params["portfolio"] = portfolio + + if status is not None: + query_params["status"] = status + + if order_id is not None: + query_params["order_id"] = order_id + + if target_symbol is not None: + query_params["target_symbol"] = target_symbol + + if trading_symbol is not None: + query_params["trading_symbol"] = trading_symbol - def get_trades(self, market=None) -> List[Trade]: + return self.trade_service.find(query_params) + + def get_trades( + self, + target_symbol=None, + trading_symbol=None, + market=None, + portfolio=None, + status=None, + ) -> List[Trade]: """ Function to get all trades. This function will return all trades that match the specified query parameters. If the market parameter is specified, the trades with the specified market will be returned. - Parameters: + Args: market: The market of the asset + portfolio: The portfolio of the asset + status: The status of the trade + target_symbol: The symbol of the asset + trading_symbol: The trading symbol of the asset Returns: List[Trade]: A list of trades that match the query parameters """ - return self.trade_service.get_trades(market) + + query_params = {} + + if market is not None: + query_params["market"] = market + + if portfolio is not None: + query_params["portfolio"] = portfolio + + if status is not None: + query_params["status"] = status + + if target_symbol is not None: + query_params["target_symbol"] = target_symbol + + if trading_symbol is not None: + query_params["trading_symbol"] = trading_symbol + + return self.trade_service.get_all({"market": market}) def get_closed_trades(self) -> List[Trade]: """ @@ -1211,7 +1320,44 @@ def get_closed_trades(self) -> List[Trade]: Returns: List[Trade]: A list of closed trades """ - return self.trade_service.get_closed_trades() + return self.trade_service.get_all({"status": TradeStatus.CLOSED.value}) + + def count_trades( + self, + target_symbol=None, + trading_symbol=None, + market=None, + portfolio=None + ) -> int: + """ + Function to count trades. This function will return the number of + trades that match the specified query parameters. + + Args: + target_symbol: The symbol of the asset + trading_symbol: The trading symbol of the asset + market: The market of the asset + portfolio: The portfolio of the asset + + Returns: + int: The number of trades that match the query parameters + """ + + query_params = {} + + if market is not None: + query_params["market"] = market + + if portfolio is not None: + query_params["portfolio"] = portfolio + + if target_symbol is not None: + query_params["target_symbol"] = target_symbol + + if trading_symbol is not None: + query_params["trading_symbol"] = trading_symbol + + return self.trade_service.count(query_params) def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]: """ @@ -1222,32 +1368,107 @@ def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]: is specified, the open trades with the specified market will be returned. - Parameters: + Args: target_symbol: The symbol of the asset market: The market of the asset Returns: List[Trade]: A list of open trades that match the query parameters """ - return self.trade_service.get_open_trades(target_symbol, market) + return self.trade_service.get_all( + { + "status": TradeStatus.OPEN.value, + "target_symbol": target_symbol, + "market": market + } + ) + + def add_stop_loss(self, trade, percentage: int) -> None: + """ + Function to add a stop loss to a trade. This function will add a + stop loss to the specified trade. If the stop loss is triggered, + the trade will be closed. + + Args: + trade: Trade - The trade to add the stop loss to + percentage: int - The stop loss of the trade + + Returns: + None + """ + self.trade_service.add_stop_loss(trade, percentage=percentage) + + def add_trailing_stop_loss(self, trade, percentage: int) -> None: + """ + Function to add a trailing stop loss to a trade. This function will + add a trailing stop loss to the specified trade. If the trailing + stop loss is triggered, the trade will be closed. - def close_trade(self, trade, market=None, precision=None) -> None: + Args: + trade: Trade - The trade to add the trailing stop loss to + trailing_stop_loss: float - The trailing stop loss of the trade + + Returns: + None + """ + self.trade_service.add_trailing_stop_loss(trade, percentage=percentage) + + def close_trade(self, trade, precision=None) -> None: """ Function to close a trade. This function will close a trade by creating a market order to sell the position. If the precision parameter is specified, the amount of the order will be rounded down to the specified precision. - Parameters: + Args: trade: Trade - The trade to close - market: str - The market of the trade - precision: float - The precision of the amount + precision: int - The precision of the amount Returns: None """ - self.trade_service.close_trade( - trade=trade, market=market, precision=precision + + trade = self.trade_service.get(trade.id) + + if TradeStatus.CLOSED.equals(trade.status): + raise OperationalException("Trade already closed.") + + if trade.remaining <= 0: + raise OperationalException("Trade has no amount to close.") + + position_id = trade.orders[0].position_id + portfolio = self.portfolio_service.find({"position": position_id}) + position = self.position_service.find( + {"portfolio": portfolio.id, "symbol": trade.target_symbol} + ) + amount = trade.remaining + + if precision is not None: + amount = RoundingService.round_down(amount, precision) + + if position.get_amount() < amount: + logger.warning( + f"Order amount {amount} is larger then amount " + f"of available {position.symbol} " + f"position: {position.get_amount()}, " + f"changing order amount to size of position" + ) + amount = position.get_amount() + + ticker = self._market_data_source_service.get_ticker( + symbol=trade.symbol, market=portfolio.market + ) + + self.order_service.create( + { + "portfolio_id": portfolio.id, + "trading_symbol": trade.trading_symbol, + "target_symbol": trade.target_symbol, + "amount": amount, + "order_side": OrderSide.SELL.value, + "order_type": OrderType.LIMIT.value, + "price": ticker["bid"], + } ) def get_number_of_positions(self): @@ -1400,3 +1621,9 @@ def get_unfilled_sell_value(self): [order.get_amount() * order.get_price() for order in pending_orders] ) + + def get_trade_service(self): + return self.trade_service + + def get_market_data_source_service(self): + return self._market_data_source_service diff --git a/investing_algorithm_framework/app/app.py b/investing_algorithm_framework/app/app.py index 25e5fe5f..a28bed4b 100644 --- a/investing_algorithm_framework/app/app.py +++ b/investing_algorithm_framework/app/app.py @@ -17,7 +17,8 @@ SQLALCHEMY_DATABASE_URI, OperationalException, \ BACKTESTING_START_DATE, BACKTESTING_END_DATE, BacktestReport, \ BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, APP_MODE, MarketCredential, \ - AppMode, BacktestDateRange + AppMode, BacktestDateRange, DATABASE_DIRECTORY_NAME, \ + BACKTESTING_INITIAL_AMOUNT, MarketDataSource from investing_algorithm_framework.infrastructure import setup_sqlalchemy, \ create_all_tables from investing_algorithm_framework.services import OrderBacktestService, \ @@ -48,6 +49,7 @@ def __init__(self, state_handler=None): self._configuration_service = None self._market_data_source_service: \ Optional[MarketDataSourceService] = None + self._market_data_sources = [] self._market_credential_service: \ Optional[MarketCredentialService] = None self._on_initialize_hooks = [] @@ -71,6 +73,7 @@ def set_config_with_dict(self, dictionary) -> None: def initialize_services(self) -> None: self._configuration_service = self.container.configuration_service() + self._configuration_service.initialize() self._market_data_source_service = \ self.container.market_data_source_service() self._market_credential_service = \ @@ -111,17 +114,40 @@ def initialize_config(self): DATABASE_NAME, "prod-database.sqlite3" ) - config = configuration_service.get_config() - - if DATABASE_DIRECTORY_PATH not in config \ - or config[DATABASE_DIRECTORY_PATH] is None: - resource_dir = config[RESOURCE_DIRECTORY] + # Set the database directory name + if Environment.BACKTEST.equals(config[ENVIRONMENT]): + configuration_service.add_value( + DATABASE_DIRECTORY_NAME, "backtest_databases" + ) configuration_service.add_value( - DATABASE_DIRECTORY_PATH, - os.path.join(resource_dir, "databases") + DATABASE_NAME, "backtest-database.sqlite3" ) + else: + configuration_service.add_value( + DATABASE_DIRECTORY_NAME, "databases" + ) + + if Environment.TEST.equals(config[ENVIRONMENT]): + configuration_service.add_value( + DATABASE_NAME, "test-database.sqlite3" + ) + elif Environment.PROD.equals(config[ENVIRONMENT]): + configuration_service.add_value( + DATABASE_NAME, "prod-database.sqlite3" + ) + else: + configuration_service.add_value( + DATABASE_NAME, "dev-database.sqlite3" + ) config = configuration_service.get_config() + resource_dir = config[RESOURCE_DIRECTORY] + database_dir_name = config.get(DATABASE_DIRECTORY_NAME) + configuration_service.add_value( + DATABASE_DIRECTORY_PATH, + os.path.join(resource_dir, database_dir_name) + ) + config = configuration_service.get_config() if SQLALCHEMY_DATABASE_URI not in config \ or config[SQLALCHEMY_DATABASE_URI] is None: @@ -158,23 +184,6 @@ def initialize(self): for data_source in self.algorithm.data_sources: self.add_market_data_source(data_source) - self.algorithm.initialize_services( - configuration_service=self.container.configuration_service(), - market_data_source_service=self.container - .market_data_source_service(), - market_credential_service=self.container - .market_credential_service(), - portfolio_service=self.container.portfolio_service(), - position_service=self.container.position_service(), - order_service=self.container.order_service(), - portfolio_configuration_service=self.container - .portfolio_configuration_service(), - market_service=self.container.market_service(), - strategy_orchestrator_service=self.container - .strategy_orchestrator_service(), - trade_service=self.container.trade_service(), - ) - # Ensure that all resource directories exist self._create_resources_if_not_exists() @@ -182,19 +191,118 @@ def initialize(self): setup_sqlalchemy(self) create_all_tables() + # Check if environment is in backtest mode + config = self.container.configuration_service().get_config() + + # Initialize services in backtest + if Environment.BACKTEST.equals(config[ENVIRONMENT]): + + configuration_service = self.container.configuration_service() + portfolio_conf_service = self.container \ + .portfolio_configuration_service() + portfolio_snap_service = self.container \ + .portfolio_snapshot_service() + market_cred_service = self.container.market_credential_service() + # Override the portfolio service with the backtest + # portfolio service + self.container.portfolio_service.override( + BacktestPortfolioService( + configuration_service=configuration_service, + market_credential_service=market_cred_service, + market_service=self.container.market_service(), + position_service=self.container.position_service(), + order_service=self.container.order_service(), + portfolio_repository=self.container.portfolio_repository(), + portfolio_configuration_service=portfolio_conf_service, + portfolio_snapshot_service=portfolio_snap_service + ) + ) + + # Get all current market data sources + market_data_sources = self._market_data_source_service \ + .get_market_data_sources() + + # Override the market data source service with the backtest market + # data source service + self.container.market_data_source_service.override( + BacktestMarketDataSourceService( + market_service=self.container.market_service(), + market_credential_service=self.container + .market_credential_service(), + configuration_service=self.container + .configuration_service(), + market_data_sources=market_data_sources + ) + ) + + portfolio_conf_service = self.container.\ + portfolio_configuration_service() + portfolio_snap_service = self.container.\ + portfolio_snapshot_service() + configuration_service = self.container.configuration_service() + market_data_source_service = self.container.\ + market_data_source_service() + # Override the order service with the backtest order service + self.container.order_service.override( + OrderBacktestService( + trade_service=self.container.trade_service(), + order_repository=self.container.order_repository(), + position_repository=self.container.position_repository(), + portfolio_repository=self.container.portfolio_repository(), + portfolio_configuration_service=portfolio_conf_service, + portfolio_snapshot_service=portfolio_snap_service, + configuration_service=configuration_service, + market_data_source_service=market_data_source_service + ) + ) + # Initialize all market credentials - market_credential_service = self.container.market_credential_service() - market_credential_service.initialize() + self._market_credential_service = self.container.\ + market_credential_service() + self._market_credential_service.initialize() - # Initialize all market data sources from registered the strategies - market_data_source_service = \ - self.container.market_data_source_service() + # Add all market data sources of the strategies to the market data + # source service + self._market_data_source_service = self.container.\ + market_data_source_service() + self._market_data_source_service.market_data_sources = \ + self._market_data_sources for strategy in self.algorithm.strategies: if strategy.market_data_sources is not None: for market_data_source in strategy.market_data_sources: - market_data_source_service.add(market_data_source) + self._market_data_source_service.add(market_data_source) + + # Initialize the market data source service + self._market_data_source_service.initialize_market_data_sources() + + portfolio_configuration_service = self.container \ + .portfolio_configuration_service() + + # Re-init the market service because the portfolio configuration + # service is a singleton + portfolio_configuration_service.market_service \ + = self.container.market_service() + + if portfolio_configuration_service.count() == 0: + raise OperationalException("No portfolios configured") + + self.algorithm.initialize_services( + configuration_service=self.container.configuration_service(), + market_data_source_service=self._market_data_source_service, + market_credential_service=self.container + .market_credential_service(), + portfolio_service=self.container.portfolio_service(), + position_service=self.container.position_service(), + order_service=self.container.order_service(), + portfolio_configuration_service=self.container + .portfolio_configuration_service(), + market_service=self.container.market_service(), + strategy_orchestrator_service=self.container + .strategy_orchestrator_service(), + trade_service=self.container.trade_service(), + ) config = self.container.configuration_service().get_config() @@ -207,32 +315,49 @@ def initialize(self): # Initialize all portfolios that are registered portfolio_configuration_service = self.container \ .portfolio_configuration_service() + portfolio_service = self.container.portfolio_service() # Throw an error if no portfolios are configured if portfolio_configuration_service.count() == 0: raise OperationalException("No portfolios configured") - # Check if all portfolios are configured - portfolio_service = self.container.portfolio_service() - synced_portfolios = [] + if Environment.BACKTEST.equals(config[ENVIRONMENT]): + initial_backtest_amount = config.get( + BACKTESTING_INITIAL_AMOUNT, None + ) - for portfolio_configuration \ - in portfolio_configuration_service.get_all(): + for portfolio_configuration \ + in portfolio_configuration_service.get_all(): - if not portfolio_service.exists( - {"identifier": portfolio_configuration.identifier} - ): - portfolio = portfolio_service\ - .create_portfolio_from_configuration( - portfolio_configuration + if not portfolio_service.exists( + {"identifier": portfolio_configuration.identifier} + ): + portfolio = ( + portfolio_service.create_portfolio_from_configuration( + portfolio_configuration, + initial_amount=initial_backtest_amount, + ) ) + else: + synced_portfolios = [] - portfolios = portfolio_service.get_all() + for portfolio_configuration \ + in portfolio_configuration_service.get_all(): - for portfolio in portfolios: + if not portfolio_service.exists( + {"identifier": portfolio_configuration.identifier} + ): + portfolio = portfolio_service\ + .create_portfolio_from_configuration( + portfolio_configuration + ) + + portfolios = portfolio_service.get_all() - if portfolio not in synced_portfolios: - self.sync(portfolio) + for portfolio in portfolios: + + if portfolio not in synced_portfolios: + self.sync(portfolio) def sync(self, portfolio): """ @@ -256,268 +381,6 @@ def sync(self, portfolio): # Sync all orders from exchange with current order history portfolio_sync_service.sync_orders(portfolio) - # Sync all trades from exchange with current trade history - portfolio_sync_service.sync_trades(portfolio) - - def _initialize_standard(self): - """ - Initialize the app for standard mode by setting the configuration - parameters for standard mode and overriding the services with the - standard services equivalents. - - Standard has the following implications: - db: sqlite - web: False - app: Standard - algorithm: Standard - """ - configuration_service = self.container.configuration_service() - resource_dir = configuration_service.config[RESOURCE_DIRECTORY] - - if resource_dir is None: - configuration_service.config[SQLALCHEMY_DATABASE_URI] = "sqlite://" - else: - resource_dir = self._create_resource_directory_if_not_exists() - configuration_service.config[DATABASE_DIRECTORY_PATH] = \ - os.path.join(resource_dir, "databases") - configuration_service.config[DATABASE_NAME] \ - = "prod-database.sqlite3" - configuration_service.config[SQLALCHEMY_DATABASE_URI] = \ - "sqlite:///" + os.path.join( - configuration_service.config[DATABASE_DIRECTORY_PATH], - configuration_service.config[DATABASE_NAME] - ) - self._create_database_if_not_exists() - - def _initialize_app_for_backtest( - self, - backtest_date_range: BacktestDateRange, - pending_order_check_interval=None, - ) -> None: - """ - Initialize the app for backtesting by setting the configuration - parameters for backtesting and overriding the services with the - backtest services equivalents. This method should only be called - before running a backtest or a set of backtests and should be called - once. - - Args: - backtest_date_range: instance of BacktestDateRange - pending_order_check_interval: The interval at which to check - pending orders (e.g. 1h, 1d, 1w) - - Return None - """ - # Set all config vars for backtesting - configuration_service = self.container.configuration_service() - configuration_service.add_value( - ENVIRONMENT, Environment.BACKTEST.value - ) - configuration_service.add_value( - BACKTESTING_START_DATE, backtest_date_range.start_date - ) - configuration_service.add_value( - BACKTESTING_END_DATE, backtest_date_range.end_date - ) - configuration_service.add_value( - DATABASE_NAME, "backtest-database.sqlite3" - ) - configuration_service.add_value( - DATABASE_DIRECTORY_PATH, - os.path.join( - configuration_service.config[ - RESOURCE_DIRECTORY - ], - "backtest_databases" - ) - ) - - if pending_order_check_interval is not None: - configuration_service.add_value( - BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, - pending_order_check_interval - ) - - # Create resource dir if not exits - self._create_resources_if_not_exists() - - def _create_backtest_database_if_not_exists(self): - """ - Create the backtest database if it does not exist. This method - should be called before running a backtest for an algorithm. - It creates the database if it does not exist. - - Parameters: - None - - Returns - None - """ - configuration_service = self.container.configuration_service() - resource_dir = configuration_service.config[RESOURCE_DIRECTORY] - - # Create the database if not exists - configuration_service.add_value( - DATABASE_NAME, "backtest-database.sqlite3" - ) - configuration_service.add_value( - DATABASE_DIRECTORY_PATH, - os.path.join(resource_dir, "databases") - ) - - database_path = os.path.join( - configuration_service.config[DATABASE_DIRECTORY_PATH], - configuration_service.config[DATABASE_NAME] - ) - - if os.path.exists(database_path): - os.remove(database_path) - - sql_alchemy_uri = \ - "sqlite:///" + os.path.join( - configuration_service.config[DATABASE_DIRECTORY_PATH], - configuration_service.config[DATABASE_NAME] - ) - - configuration_service.add_value( - SQLALCHEMY_DATABASE_URI, sql_alchemy_uri - ) - self._create_database_if_not_exists() - setup_sqlalchemy(self) - create_all_tables() - - def _initialize_backtest_data_sources(self, algorithm): - """ - Initialize the backtest data sources for the algorithm. This method - should be called before running a backtest. It initializes the - backtest data sources for the algorithm. It takes all registered - data sources and converts them to backtest equivalents - - Args: - algorithm: The algorithm to initialize for backtesting - - Returns - None - """ - - market_data_sources = self._market_data_source_service \ - .get_market_data_sources() - backtest_market_data_sources = [] - - if algorithm.data_sources is not None \ - and len(algorithm.data_sources) > 0: - - for data_source in algorithm.data_sources: - self.add_market_data_source(data_source) - - if market_data_sources is not None: - backtest_market_data_sources = [ - market_data_source.to_backtest_market_data_source() - for market_data_source in market_data_sources - if market_data_source is not None - ] - - for market_data_source in backtest_market_data_sources: - if market_data_source is not None: - market_data_source.config = self.config - - # Override the market data source service with the backtest market - # data source service - self.container.market_data_source_service.override( - BacktestMarketDataSourceService( - market_data_sources=backtest_market_data_sources, - market_service=self.container.market_service(), - market_credential_service=self.container - .market_credential_service(), - configuration_service=self.container - .configuration_service(), - ) - ) - - # Set all data sources to the algorithm - algorithm.add_data_sources(backtest_market_data_sources) - - def _initialize_algorithm_for_backtest(self, algorithm): - """ - Function to initialize the algorithm for backtesting. This method - should be called before running a backtest. It initializes the - all data sources to backtest data sources and overrides the services - with the backtest services equivalents. - - Parameters: - algorithm: The algorithm to initialize for backtesting - - Returns - None - """ - self._create_backtest_database_if_not_exists() - self._initialize_backtest_data_sources(algorithm) - - # Override the portfolio service with the backtest portfolio service - self.container.portfolio_service.override( - BacktestPortfolioService( - configuration_service=self.container.configuration_service(), - market_credential_service=self.container - .market_credential_service(), - market_service=self.container.market_service(), - position_service=self.container.position_service(), - order_service=self.container.order_service(), - portfolio_repository=self.container.portfolio_repository(), - portfolio_configuration_service=self.container - .portfolio_configuration_service(), - portfolio_snapshot_service=self.container - .portfolio_snapshot_service(), - ) - ) - - # Override the order service with the backtest order service - market_data_source_service = self.container \ - .market_data_source_service() - self.container.order_service.override( - OrderBacktestService( - order_repository=self.container.order_repository(), - position_repository=self.container.position_repository(), - portfolio_repository=self.container.portfolio_repository(), - portfolio_configuration_service=self.container - .portfolio_configuration_service(), - portfolio_snapshot_service=self.container - .portfolio_snapshot_service(), - configuration_service=self.container.configuration_service(), - market_data_source_service=market_data_source_service - ) - ) - - portfolio_configuration_service = self.container \ - .portfolio_configuration_service() - - # Re-init the market service because the portfolio configuration - # service is a singleton - portfolio_configuration_service.market_service \ - = self.container.market_service() - - if portfolio_configuration_service.count() == 0: - raise OperationalException("No portfolios configured") - - strategy_orchestrator_service = \ - self.container.strategy_orchestrator_service() - market_credential_service = self.container.market_credential_service() - market_data_source_service = \ - self.container.market_data_source_service() - # Initialize all services in the algorithm - algorithm.initialize_services( - configuration_service=self.container.configuration_service(), - portfolio_configuration_service=self.container - .portfolio_configuration_service(), - portfolio_service=self.container.portfolio_service(), - position_service=self.container.position_service(), - order_service=self.container.order_service(), - market_service=self.container.market_service(), - strategy_orchestrator_service=strategy_orchestrator_service, - market_credential_service=market_credential_service, - market_data_source_service=market_data_source_service, - trade_service=self.container.trade_service(), - ) - def run( self, payload: dict = None, @@ -555,6 +418,16 @@ def run( None """ try: + configuration_service = self.container.configuration_service() + config = configuration_service.get_config() + + # Run method should never be called with environment set to + # backtest, if it is, then set the environment to prod + if config[ENVIRONMENT] == Environment.BACKTEST.value: + configuration_service.add_value( + ENVIRONMENT, Environment.PROD.value + ) + self.initialize_config() # Load the state if a state handler is provided @@ -589,12 +462,11 @@ def run( target=self._flask_app.run, kwargs={"port": 8080} ) - flask_thread.setDaemon(True) + flask_thread.daemon = True flask_thread.start() self.algorithm.start(number_of_iterations=number_of_iterations) number_of_iterations_since_last_orders_check = 1 - self.algorithm.check_pending_orders() try: while self.algorithm.running: @@ -608,6 +480,8 @@ def run( except KeyboardInterrupt: exit(0) finally: + self.algorithm.stop() + # Upload state if state handler is provided if self._state_handler is not None: logger.info("Detected state handler, saving state") @@ -729,35 +603,6 @@ def _create_resources_if_not_exists(self): "Could not create database directory" ) - def _create_database_if_not_exists(self): - configuration_service = self.container.configuration_service() - database_dir = configuration_service.config \ - .get(DATABASE_DIRECTORY_PATH, None) - - if database_dir is None: - return - - config = configuration_service.get_config() - database_name = config[DATABASE_NAME] - - if database_name is None: - return - - database_path = os.path.join(database_dir, database_name) - - if not os.path.exists(database_path): - - if not os.path.isdir(database_dir): - os.makedirs(database_dir) - - try: - open(database_path, 'w').close() - except OSError as e: - logger.error(e) - raise OperationalException( - "Could not create database directory" - ) - def get_portfolio_configurations(self): return self.algorithm.get_portfolio_configurations() @@ -795,14 +640,30 @@ def run_backtest( if self.algorithm is None: raise OperationalException("No algorithm registered") - self._initialize_app_for_backtest( - backtest_date_range=backtest_date_range, - pending_order_check_interval=pending_order_check_interval, + # Add backtest configuration to the config + self.set_config_with_dict({ + ENVIRONMENT: Environment.BACKTEST.value, + BACKTESTING_START_DATE: backtest_date_range.start_date, + BACKTESTING_END_DATE: backtest_date_range.end_date, + DATABASE_NAME: "backtest-database.sqlite3", + DATABASE_DIRECTORY_NAME: "backtest_databases", + BACKTESTING_PENDING_ORDER_CHECK_INTERVAL: ( + pending_order_check_interval + ), + BACKTESTING_INITIAL_AMOUNT: initial_amount + }) + + self.initialize_config() + config = self._configuration_service.get_config() + path = os.path.join( + config[DATABASE_DIRECTORY_PATH], + config[DATABASE_NAME] ) + # Remove the previous backtest db + if os.path.exists(path): + os.remove(path) - self._initialize_algorithm_for_backtest( - algorithm=self.algorithm - ) + self.initialize() backtest_service = self.container.backtest_service() @@ -812,7 +673,6 @@ def run_backtest( initial_amount=initial_amount, backtest_date_range=backtest_date_range ) - config = self.container.configuration_service().get_config() if output_directory is None: @@ -823,12 +683,12 @@ def run_backtest( backtest_service.write_report_to_json( report=report, output_directory=output_directory ) - return report def run_backtests( self, algorithms, + initial_amount=None, date_ranges: List[BacktestDateRange] = None, pending_order_check_interval=None, output_directory=None, @@ -842,6 +702,7 @@ def run_backtests( Algorithms: List[Algorithm] - The algorithms to run backtests for date_ranges: List[BacktestDateRange] - The date ranges to run the backtests for + initial_amount: The initial amount to start the backtest with. pending_order_check_interval: str - The interval at which to check pending orders output_directory: str - The directory to write the backtest @@ -862,11 +723,6 @@ def run_backtests( for date_range in date_ranges: date_range: BacktestDateRange = date_range - self._initialize_app_for_backtest( - backtest_date_range=date_range, - pending_order_check_interval=pending_order_check_interval, - ) - print( f"{COLOR_YELLOW}Running backtests for date " f"range:{COLOR_RESET} {COLOR_GREEN}{date_range.name} " @@ -897,8 +753,30 @@ def run_backtests( ) reports.append(report) continue + self.algorithm = algorithm + self.set_config_with_dict({ + ENVIRONMENT: Environment.BACKTEST.value, + BACKTESTING_START_DATE: date_range.start_date, + BACKTESTING_END_DATE: date_range.end_date, + DATABASE_NAME: "backtest-database.sqlite3", + DATABASE_DIRECTORY_NAME: "backtest_databases", + BACKTESTING_PENDING_ORDER_CHECK_INTERVAL: ( + pending_order_check_interval + ) + }) + self.initialize_config() + + config = self._configuration_service.get_config() - self._initialize_algorithm_for_backtest(algorithm) + path = os.path.join( + config[DATABASE_DIRECTORY_PATH], + config[DATABASE_NAME] + ) + # Remove the previous backtest db + if os.path.exists(path): + os.remove(path) + + self.initialize() backtest_service = self.container.backtest_service() backtest_service.resource_directory = self.config[ RESOURCE_DIRECTORY @@ -907,7 +785,9 @@ def run_backtests( # Run the backtest with the backtest_service # and collect the report report = backtest_service.run_backtest( - algorithm=algorithm, backtest_date_range=date_range + algorithm=self.algorithm, + initial_amount=initial_amount, + backtest_date_range=date_range ) # Add date range name to report if present @@ -927,8 +807,35 @@ def run_backtests( return reports def add_market_data_source(self, market_data_source): - market_data_source.config = self.config - self._market_data_source_service.add(market_data_source) + """ + Function to add a market data source to the app. The market data + source should be an instance of MarketDataSource. + + This is a seperate function from the market data source service. This + is because the market data source service can be re-initialized. + Therefore we need a persistent list of market data sources in the app. + + Args: + market_data_source: Instance of MarketDataSource + + Returns: + None + """ + + # Check if the market data source is an instance of MarketDataSource + if not isinstance(market_data_source, MarketDataSource): + return + + # Check if there is already a market data source with the same + # identifier + for existing_market_data_source in self._market_data_sources: + if existing_market_data_source.get_identifier() == \ + market_data_source.get_identifier(): + return + + market_data_source.market_credential_service = \ + self._market_credential_service + self._market_data_sources.append(market_data_source) def add_market_credential(self, market_credential: MarketCredential): market_credential.market = market_credential.market.upper() @@ -955,3 +862,7 @@ def after_initialize(self, app_hook: AppHook): app_hook = app_hook() self._on_after_initialize_hooks.append(app_hook) + + def clear(self) -> None: + self.algorithm = Algorithm() + self.mark diff --git a/investing_algorithm_framework/app/strategy.py b/investing_algorithm_framework/app/strategy.py index 37362d84..e0c47646 100644 --- a/investing_algorithm_framework/app/strategy.py +++ b/investing_algorithm_framework/app/strategy.py @@ -1,9 +1,12 @@ from typing import List +import pandas as pd +from datetime import datetime, timezone + from investing_algorithm_framework.domain import OperationalException, Position from investing_algorithm_framework.domain import \ - TimeUnit, StrategyProfile, Trade + TimeUnit, StrategyProfile, Trade, ENVIRONMENT, Environment, \ + BACKTESTING_INDEX_DATETIME from .algorithm import Algorithm -import pandas as pd class TradingStrategy: @@ -67,16 +70,27 @@ def __init__( # context initialization self._context = None + self._last_run = None def run_strategy(self, algorithm, market_data): self.algorithm = algorithm + config = self.algorithm.get_config() + + if config[ENVIRONMENT] == Environment.BACKTEST.value: + self._update_trades_and_orders_for_backtest(market_data) + else: + self._update_trades_and_orders(market_data) - # Check pending orders before running the strategy - algorithm.check_pending_orders() + self._check_stop_losses() # Run user defined strategy self.apply_strategy(algorithm=algorithm, market_data=market_data) + if config[ENVIRONMENT] == Environment.BACKTEST.value: + self._last_run = config[BACKTESTING_INDEX_DATETIME] + else: + self._last_run = datetime.now(tz=timezone.utc) + def apply_strategy(self, algorithm, market_data): if self.decorated: self.decorated(algorithm=algorithm, market_data=market_data) @@ -92,6 +106,44 @@ def strategy_profile(self): market_data_sources=self.market_data_sources ) + def _update_trades_and_orders(self, market_data): + self.algorithm.order_service.check_pending_orders() + self.algorithm.trade_service\ + .update_trades_with_market_data(market_data) + + def _update_trades_and_orders_for_backtest(self, market_data): + self.algorithm.order_service.check_pending_orders(market_data) + self.algorithm.trade_service\ + .update_trades_with_market_data(market_data) + + def _check_pending_orders(self, market_data): + """ + Check if there are any pending orders that need to be executed + """ + pass + # self.algorithm.check_pending_orders() + + def _check_stop_losses(self): + """ + Check if there are any stop losses that result in trades being closed. + """ + trade_service = self.algorithm.trade_service + triggered_trades = trade_service.get_triggered_stop_losses() + + for trade in triggered_trades: + trade_service.update(trade.id, {"stop_loss_triggered": True}) + self.algorithm.close_trade( + trade + ) + + triggered_trades = trade_service.get_triggered_trailing_stop_losses() + + for trade in triggered_trades: + trade_service.update(trade.id, {"stop_loss_triggered": True}) + self.algorithm.close_trade( + trade + ) + def on_trade_closed(self, algorithm: Algorithm, trade: Trade): pass @@ -222,7 +274,7 @@ def has_open_orders( """ Check if there are open orders for a given symbol - Parameters: + Args: target_symbol (str): The symbol of the asset e.g BTC if the asset is BTC/USDT identifier (str): The identifier of the portfolio @@ -256,7 +308,7 @@ def create_limit_order( a limit order and execute it if the execute parameter is set to True. If the validate parameter is set to True, the order will be validated - Parameters: + Args: target_symbol: The symbol of the asset to trade price: The price of the asset order_side: The side of the order @@ -313,7 +365,7 @@ def create_market_order( order and execute it if the execute parameter is set to True. If the validate parameter is set to True, the order will be validated - Parameters: + Args: target_symbol: The symbol of the asset to trade order_side: The side of the order amount: The amount of the asset to trade @@ -345,7 +397,7 @@ def close_position( parameter is specified, the amount of the order will be rounded down to the specified precision. - Parameters: + Args: symbol: The symbol of the asset market: The market of the asset identifier: The identifier of the portfolio @@ -385,7 +437,7 @@ def get_positions( If the amount_lte parameter is specified, the positions with an amount less than or equal to the specified amount will be returned. - Parameters: + Args: market: The market of the portfolio where the positions are identifier: The identifier of the portfolio amount_gt: The amount of the asset must be greater than this @@ -413,7 +465,7 @@ def get_trades(self, market=None) -> List[Trade]: that match the specified query parameters. If the market parameter is specified, the trades with the specified market will be returned. - Parameters: + Args: market: The market of the asset Returns: @@ -440,7 +492,7 @@ def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]: is specified, the open trades with the specified market will be returned. - Parameters: + Args: target_symbol: The symbol of the asset market: The market of the asset @@ -456,7 +508,7 @@ def close_trade(self, trade, market=None, precision=None) -> None: parameter is specified, the amount of the order will be rounded down to the specified precision. - Parameters: + Args: trade: Trade - The trade to close market: str - The market of the trade precision: float - The precision of the amount @@ -488,7 +540,7 @@ def get_position( specified, the position of the specified portfolio will be returned. - Parameters: + Args: symbol: The symbol of the asset that represents the position market: The market of the portfolio where the position is located identifier: The identifier of the portfolio @@ -517,7 +569,7 @@ def has_position( True if a position exists, False otherwise. This function will check if the amount > 0 condition by default. - Parameters: + Args: param symbol: The symbol of the asset param market: The market of the asset param identifier: The identifier of the portfolio @@ -548,7 +600,7 @@ def has_balance(self, symbol, amount, market=None): portfolio has enough balance to create an order, False otherwise. - Parameters: + Args: symbol: The symbol of the asset amount: The amount of the asset market: The market of the asset @@ -557,3 +609,12 @@ def has_balance(self, symbol, amount, market=None): Boolean: True if the portfolio has enough balance """ return self.algorithm.has_balance(symbol, amount, market) + + def last_run(self) -> datetime: + """ + Function to get the last run of the strategy + + Returns: + DateTime: The last run of the strategy + """ + return self.algorithm.last_run() diff --git a/investing_algorithm_framework/dependency_container.py b/investing_algorithm_framework/dependency_container.py index c1e469c0..385b0149 100644 --- a/investing_algorithm_framework/dependency_container.py +++ b/investing_algorithm_framework/dependency_container.py @@ -3,7 +3,7 @@ from investing_algorithm_framework.app.algorithm import Algorithm from investing_algorithm_framework.infrastructure import SQLOrderRepository, \ SQLPositionRepository, SQLPortfolioRepository, \ - SQLPortfolioSnapshotRepository, \ + SQLPortfolioSnapshotRepository, SQLTradeRepository, \ SQLPositionSnapshotRepository, PerformanceService, CCXTMarketService from investing_algorithm_framework.services import OrderService, \ PositionService, PortfolioService, StrategyOrchestratorService, \ @@ -40,14 +40,16 @@ class DependencyContainer(containers.DeclarativeContainer): portfolio_snapshot_repository = providers.Factory( SQLPortfolioSnapshotRepository ) + trade_repository = providers.Factory(SQLTradeRepository) market_service = providers.Factory( CCXTMarketService, market_credential_service=market_credential_service, ) - market_data_source_service = providers.Factory( + market_data_source_service = providers.Singleton( MarketDataSourceService, market_service=market_service, market_credential_service=market_credential_service, + configuration_service=configuration_service ) position_snapshot_service = providers.Factory( PositionSnapshotService, @@ -64,6 +66,21 @@ class DependencyContainer(containers.DeclarativeContainer): portfolio_repository=portfolio_repository, position_repository=position_repository, ) + position_service = providers.Factory( + PositionService, + repository=position_repository, + market_service=market_service, + market_credential_service=market_credential_service, + order_repository=order_repository, + ) + trade_service = providers.Factory( + TradeService, + configuration_service=configuration_service, + trade_repository=trade_repository, + portfolio_repository=portfolio_repository, + position_repository=position_repository, + market_data_source_service=market_data_source_service, + ) order_service = providers.Factory( OrderService, configuration_service=configuration_service, @@ -73,14 +90,8 @@ class DependencyContainer(containers.DeclarativeContainer): market_service=market_service, market_credential_service=market_credential_service, portfolio_configuration_service=portfolio_configuration_service, - portfolio_snapshot_service=portfolio_snapshot_service - ) - position_service = providers.Factory( - PositionService, - repository=position_repository, - market_service=market_service, - market_credential_service=market_credential_service, - order_repository=order_repository, + portfolio_snapshot_service=portfolio_snapshot_service, + trade_service=trade_service, ) portfolio_service = providers.Factory( PortfolioService, @@ -93,18 +104,11 @@ class DependencyContainer(containers.DeclarativeContainer): portfolio_configuration_service=portfolio_configuration_service, portfolio_snapshot_service=portfolio_snapshot_service, ) - trade_service = providers.Factory( - TradeService, - portfolio_repository=portfolio_repository, - order_service=order_service, - market_data_source_service=market_data_source_service, - position_service=position_service, - ) portfolio_sync_service = providers.Factory( PortfolioSyncService, trade_service=trade_service, configuration_service=configuration_service, - order_repository=order_repository, + order_service=order_service, position_repository=position_repository, portfolio_repository=portfolio_repository, portfolio_configuration_service=portfolio_configuration_service, @@ -113,10 +117,12 @@ class DependencyContainer(containers.DeclarativeContainer): ) strategy_orchestrator_service = providers.Factory( StrategyOrchestratorService, - market_data_source_service=market_data_source_service + market_data_source_service=market_data_source_service, + configuration_service=configuration_service, ) performance_service = providers.Factory( PerformanceService, + trade_repository=trade_repository, order_repository=order_repository, position_repository=position_repository, portfolio_repository=portfolio_repository @@ -130,6 +136,7 @@ class DependencyContainer(containers.DeclarativeContainer): position_repository=position_repository, market_data_source_service=market_data_source_service, portfolio_configuration_service=portfolio_configuration_service, + strategy_orchestrator_service=strategy_orchestrator_service, ) algorithm = providers.Factory( Algorithm, diff --git a/investing_algorithm_framework/domain/__init__.py b/investing_algorithm_framework/domain/__init__.py index 76fcba7d..6c682cf4 100644 --- a/investing_algorithm_framework/domain/__init__.py +++ b/investing_algorithm_framework/domain/__init__.py @@ -7,7 +7,8 @@ BACKTEST_DATA_DIRECTORY_NAME, TICKER_DATA_TYPE, OHLCV_DATA_TYPE, \ CURRENT_UTC_DATETIME, BACKTESTING_END_DATE, SYMBOLS, \ CCXT_DATETIME_FORMAT_WITH_TIMEZONE, RESERVED_BALANCES, \ - BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, APP_MODE + BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, APP_MODE, \ + DATABASE_DIRECTORY_NAME, BACKTESTING_INITIAL_AMOUNT from .data_structures import PeekableQueue from .decimal_parsing import parse_decimal_to_string, parse_string_to_decimal from .exceptions import OperationalException, ApiException, \ @@ -17,7 +18,8 @@ PortfolioConfiguration, Portfolio, Position, Order, TradeStatus, \ BacktestReport, PortfolioSnapshot, StrategyProfile, \ BacktestPosition, Trade, MarketCredential, PositionSnapshot, \ - BacktestReportsEvaluation, AppMode, BacktestDateRange, DateRange + BacktestReportsEvaluation, AppMode, BacktestDateRange, DateRange, \ + MarketDataType from .services import TickerMarketDataSource, OrderBookMarketDataSource, \ OHLCVMarketDataSource, BacktestMarketDataSource, MarketDataSource, \ MarketService, MarketCredentialService, AbstractPortfolioSyncService, \ @@ -119,5 +121,8 @@ "convert_polars_to_pandas", "DateRange", "get_backtest_report", - "DEFAULT_LOGGING_CONFIG" + "DEFAULT_LOGGING_CONFIG", + "DATABASE_DIRECTORY_NAME", + "BACKTESTING_INITIAL_AMOUNT", + "MarketDataType" ] diff --git a/investing_algorithm_framework/domain/constants.py b/investing_algorithm_framework/domain/constants.py index 25ace850..f880a7b0 100644 --- a/investing_algorithm_framework/domain/constants.py +++ b/investing_algorithm_framework/domain/constants.py @@ -8,6 +8,7 @@ DATABASE_NAME = 'DATABASE_NAME' DATABASE_TYPE = 'DATABASE_TYPE' DATABASE_DIRECTORY_PATH = 'DATABASE_DIRECTORY_PATH' +DATABASE_DIRECTORY_NAME = 'DATABASE_DIRECTORY_NAME' DATABASE_URL = 'DATABASE_URL' DEFAULT_DATABASE_NAME = "database" @@ -54,16 +55,14 @@ STRATEGIES = "STRATEGIES" APPLICATION_CONFIGURED = "APPLICATION_CONFIGURED" ACTION = "ACTION" - DEFAULT_PER_PAGE_VALUE = 10 DEFAULT_PAGE_VALUE = 1 ITEMIZE = 'itemize' ITEMIZED = 'itemized' PAGE = 'page' PER_PAGE = 'per_page' - DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" -DATETIME_FORMAT_BACKTESTING = "%Y-%m-%d:%H:%M" +DATETIME_FORMAT_BACKTESTING = "%Y-%m-%d-%H-%M" CCXT_DATETIME_FORMAT_WITH_TIMEZONE = "%Y-%m-%dT%H:%M:%S.%fZ" CCXT_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" BACKTESTING_FLAG = "BACKTESTING" @@ -72,6 +71,7 @@ BACKTESTING_END_DATE = "BACKTESTING_END_DATE" BACKTESTING_PENDING_ORDER_CHECK_INTERVAL \ = "BACKTESTING_PENDING_ORDER_CHECK_INTERVAL" +BACKTESTING_INITIAL_AMOUNT = "BACKTESTING_INITIAL_AMOUNT" TICKER_DATA_TYPE = "TICKER" OHLCV_DATA_TYPE = "OHLCV" CURRENT_UTC_DATETIME = "CURRENT_UTC_DATETIME" diff --git a/investing_algorithm_framework/domain/models/__init__.py b/investing_algorithm_framework/domain/models/__init__.py index d2ddcc0f..69a11f90 100644 --- a/investing_algorithm_framework/domain/models/__init__.py +++ b/investing_algorithm_framework/domain/models/__init__.py @@ -13,6 +13,7 @@ from .trading_data_types import TradingDataType from .trading_time_frame import TradingTimeFrame from .date_range import DateRange +from .market_data_type import MarketDataType __all__ = [ "OrderStatus", @@ -39,4 +40,5 @@ "AppMode", "BacktestDateRange", "DateRange", + "MarketDataType" ] diff --git a/investing_algorithm_framework/domain/models/backtesting/backtest_report.py b/investing_algorithm_framework/domain/models/backtesting/backtest_report.py index e0e87258..563ae8bb 100644 --- a/investing_algorithm_framework/domain/models/backtesting/backtest_report.py +++ b/investing_algorithm_framework/domain/models/backtesting/backtest_report.py @@ -424,7 +424,10 @@ def to_dict(self): "average_trade_duration": self.average_trade_duration, "average_trade_size": self.average_trade_size, "positions": [position.to_dict() for position in self.positions], - "trades": [trade.to_dict() for trade in self.trades], + "trades": [ + trade.to_dict(datetime_format=DATETIME_FORMAT) + for trade in self.trades + ], "orders": [ order.to_dict(datetime_format=DATETIME_FORMAT) for order in self.orders diff --git a/investing_algorithm_framework/domain/models/market_data_type.py b/investing_algorithm_framework/domain/models/market_data_type.py new file mode 100644 index 00000000..a5edae19 --- /dev/null +++ b/investing_algorithm_framework/domain/models/market_data_type.py @@ -0,0 +1,46 @@ +from enum import Enum + + +class MarketDataType(Enum): + OHLCV = "OHLCV" + TICKER = "TICKER" + ORDER_BOOK = "ORDER_BOOK" + CUSTOM = "CUSTOM" + + @staticmethod + def from_string(value: str): + + if isinstance(value, str): + + for entry in MarketDataType: + + if value.upper() == entry.value: + return entry + + raise ValueError( + f"Could not convert {value} to MarketDataType" + ) + + @staticmethod + def from_value(value): + + if isinstance(value, str): + return MarketDataType.from_string(value) + + if isinstance(value, MarketDataType): + + for entry in MarketDataType: + + if value == entry: + return entry + + raise ValueError( + f"Could not convert {value} to TimeFrame" + ) + + def equals(self, other): + + if isinstance(other, Enum): + return self.value == other.value + else: + return MarketDataType.from_string(other) == self diff --git a/investing_algorithm_framework/domain/models/order/order.py b/investing_algorithm_framework/domain/models/order/order.py index e76885f8..5bfd607a 100644 --- a/investing_algorithm_framework/domain/models/order/order.py +++ b/investing_algorithm_framework/domain/models/order/order.py @@ -28,16 +28,12 @@ def __init__( target_symbol=None, trading_symbol=None, price=None, - net_gain=0, created_at=None, updated_at=None, - trade_closed_at=None, - trade_closed_price=None, - trade_closed_amount=None, external_id=None, + cost=None, filled=None, remaining=None, - cost=None, fee=None, position_id=None, order_fee=None, @@ -70,21 +66,17 @@ def __init__( self.status = OrderStatus.from_value(status).value self.position_id = position_id self.amount = amount - self.net_gain = net_gain - self.trade_closed_at = trade_closed_at - self.trade_closed_price = trade_closed_price - self.trade_closed_amount = trade_closed_amount self.created_at = created_at self.updated_at = updated_at self.filled = filled self.remaining = remaining - self.cost = cost self.fee = fee self._available_amount = self.filled self.order_fee = order_fee self.order_fee_currency = order_fee_currency self.order_fee_rate = order_fee_rate self.id = id + self.cost = cost def get_id(self): return self.id @@ -149,38 +141,6 @@ def set_amount(self, amount): def set_external_id(self, external_id): self.external_id = external_id - def get_net_gain(self): - - if self.net_gain is None: - return 0 - - return self.net_gain - - def set_net_gain(self, net_gain): - self.net_gain = net_gain - - def get_trade_closed_at(self): - return self.trade_closed_at - - def set_trade_closed_at(self, trade_closed_at): - self.trade_closed_at = trade_closed_at - - def get_trade_closed_price(self): - return self.trade_closed_price - - def set_trade_closed_price(self, trade_closed_price): - self.trade_closed_price = trade_closed_price - - def get_trade_closed_amount(self): - - if self.trade_closed_amount is not None: - return self.trade_closed_amount - - return 0 - - def set_trade_closed_amount(self, trade_closed_amount): - self.trade_closed_amount = trade_closed_amount - def get_created_at(self): return self.created_at @@ -213,16 +173,6 @@ def get_remaining(self): def set_remaining(self, remaining): self.remaining = remaining - def get_cost(self): - - if self.cost is None: - return 0 - - return self.cost - - def set_cost(self, cost): - self.cost = cost - def get_fee(self): return self.fee @@ -257,12 +207,9 @@ def to_dict(self, datetime_format=None): if self.created_at else None updated_at = self.updated_at.strftime(datetime_format) \ if self.updated_at else None - trade_closed_at = self.trade_closed_at.strftime(datetime_format) \ - if self.trade_closed_at else None else: created_at = self.created_at updated_at = self.updated_at - trade_closed_at = self.trade_closed_at return { "external_id": self.external_id, @@ -273,14 +220,11 @@ def to_dict(self, datetime_format=None): "status": self.status, "price": self.price, "amount": self.amount, - "net_gain": self.net_gain, - "trade_closed_at": trade_closed_at, - "trade_closed_price": self.trade_closed_price, "created_at": created_at, "updated_at": updated_at, + "cost": self.cost, "filled": self.filled, "remaining": self.remaining, - "cost": self.cost, "order_fee_currency": self.order_fee_currency, "order_fee_rate": self.order_fee_rate, "order_fee": self.order_fee, @@ -316,7 +260,6 @@ def from_dict(data: dict): order_side=data.get("order_side", None), filled=data.get("filled", None), remaining=data.get("remaining", None), - cost=data.get("cost", None), fee=data.get("fee", None), created_at=created_at, updated_at=updated_at, @@ -353,11 +296,11 @@ def from_ccxt_order(ccxt_order): price=ccxt_order.get("price", None), amount=ccxt_order.get("amount", None), status=status, + cost=ccxt_order.get("cost", None), order_type=ccxt_order.get("type", None), order_side=ccxt_order.get("side", None), filled=ccxt_order.get("filled", None), remaining=ccxt_order.get("remaining", None), - cost=ccxt_order.get("cost", None), order_fee=order_fee, order_fee_currency=order_fee_currency, order_fee_rate=order_fee_rate, @@ -375,7 +318,6 @@ def __repr__(self): id=id_value, price=self.get_price(), amount=self.get_amount(), - net_gain=self.get_net_gain(), external_id=self.external_id, status=self.status, target_symbol=self.target_symbol, @@ -384,10 +326,6 @@ def __repr__(self): order_type=self.order_type, filled=self.get_filled(), remaining=self.get_remaining(), - cost=self.get_cost(), - trade_closed_at=self.get_trade_closed_at(), - trade_closed_price=self.get_trade_closed_price(), - trade_closed_amount=self.get_trade_closed_amount(), created_at=self.get_created_at(), updated_at=self.get_updated_at(), ) diff --git a/investing_algorithm_framework/domain/models/order/order_status.py b/investing_algorithm_framework/domain/models/order/order_status.py index 7e838ae0..6fc382cf 100644 --- a/investing_algorithm_framework/domain/models/order/order_status.py +++ b/investing_algorithm_framework/domain/models/order/order_status.py @@ -18,7 +18,7 @@ def from_string(value: str): if value.upper() == order_type.value: return order_type - raise ValueError("Could not convert value to OrderStatus") + raise ValueError(f"Could not convert value {value} to OrderStatus") @staticmethod def from_value(value): diff --git a/investing_algorithm_framework/domain/models/time_frame.py b/investing_algorithm_framework/domain/models/time_frame.py index 20d08692..fce76746 100644 --- a/investing_algorithm_framework/domain/models/time_frame.py +++ b/investing_algorithm_framework/domain/models/time_frame.py @@ -123,3 +123,24 @@ def amount_of_minutes(self): if self.equals(TimeFrame.ONE_MONTH): return 40320 + + # Add comparison methods for ordering + def __lt__(self, other): + if isinstance(other, TimeFrame): + return self.amount_of_minutes < other.amount_of_minutes + raise TypeError(f"Cannot compare TimeFrame with {type(other)}") + + def __le__(self, other): + if isinstance(other, TimeFrame): + return self.amount_of_minutes <= other.amount_of_minutes + raise TypeError(f"Cannot compare TimeFrame with {type(other)}") + + def __gt__(self, other): + if isinstance(other, TimeFrame): + return self.amount_of_minutes > other.amount_of_minutes + raise TypeError(f"Cannot compare TimeFrame with {type(other)}") + + def __ge__(self, other): + if isinstance(other, TimeFrame): + return self.amount_of_minutes >= other.amount_of_minutes + raise TypeError(f"Cannot compare TimeFrame with {type(other)}") diff --git a/investing_algorithm_framework/domain/models/trade/trade.py b/investing_algorithm_framework/domain/models/trade/trade.py index 582558e2..40266694 100644 --- a/investing_algorithm_framework/domain/models/trade/trade.py +++ b/investing_algorithm_framework/domain/models/trade/trade.py @@ -1,13 +1,7 @@ -from datetime import datetime -from typing import List - -import polars as pl -from polars import DataFrame - -from investing_algorithm_framework.domain.constants import DATETIME_FORMAT -from investing_algorithm_framework.domain.exceptions import \ - OperationalException from investing_algorithm_framework.domain.models.base_model import BaseModel +from investing_algorithm_framework.domain.models.order import OrderSide +from investing_algorithm_framework.domain.models.trade.trade_status import \ + TradeStatus class Trade(BaseModel): @@ -24,264 +18,279 @@ class Trade(BaseModel): A single sell order can close multiple buy orders. Also, a single buy order can be closed by multiple sell orders. + + Attributes: + id (int): the id of the trade + orders (List[Order]): the orders of the trade + target_symbol (str): the target symbol of the trade + trading_symbol (str): the trading symbol of the trade + closed_at (datetime): the datetime when the trade was closed + opened_at (datetime): the datetime when the trade was opened + open_price (float): the open price of the trade + amount (float): the amount of the trade + remaining (float): the remaining amount of the trade + net_gain (float): the net gain of the trade + last_reported_price (float): the last reported price of the trade + created_at (datetime): the datetime when the trade was created + updated_at (datetime): the datetime when the trade was last updated + status (str): the status of the trade + stop_loss_percentage (float): the stop loss percentage of + the trade + trailing_stop_loss_percentage (float): the trailing stop + loss percentage """ + def __init__( self, - buy_order_id, + id, + orders, target_symbol, trading_symbol, - amount, - open_price, + closed_at, opened_at, - closed_price=None, - closed_at=None, - current_price=None, - sell_order_id=None, + open_price, + amount, + cost, + remaining, + status, + net_gain=0, + last_reported_price=None, + high_water_mark=None, + updated_at=None, + stop_loss_percentage=None, + trailing_stop_loss_percentage=None, + stop_loss_triggered=False, ): - self._target_symbol = target_symbol - self._trading_symbol = trading_symbol - self._amount = amount - self._open_price = open_price - self._closed_price = closed_price - self._closed_at = closed_at - self._opened_at = opened_at - self._trading_symbol = trading_symbol - self._current_price = current_price - self._buy_order_id = buy_order_id - self._sell_order_id = sell_order_id, + self.id = id + self.orders = orders + self.target_symbol = target_symbol + self.trading_symbol = trading_symbol + self.closed_at = closed_at + self.opened_at = opened_at + self.open_price = open_price + self.amount = amount + self.cost = cost + self.remaining = remaining + self.net_gain = net_gain + self.last_reported_price = last_reported_price + self.high_water_mark = high_water_mark + self.status = status + self.updated_at = updated_at + self.stop_loss_percentage = stop_loss_percentage + self.trailing_stop_loss_percentage = trailing_stop_loss_percentage + self.stop_loss_triggered = stop_loss_triggered @property - def buy_order_id(self): - return self._buy_order_id + def closed_prices(self): + return [ + order.price for order in self.orders + if order.order_side == OrderSide.SELL.value + ] @property - def sell_order_id(self): - return self._sell_order_id + def buy_order(self): - @property - def target_symbol(self): - return self._target_symbol + if self.orders is None: + return - @property - def trading_symbol(self): - return self._trading_symbol + return [ + order for order in self.orders + if order.order_side == OrderSide.BUY.value + ][0] @property def symbol(self): - return f"{self.target_symbol}/{self.trading_symbol}" - - def get_symbol(self): - return f"{self.target_symbol}/{self.trading_symbol}" + return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}" @property - def amount(self): - return self._amount - - def get_amount(self): - return self.amount - - @property - def open_price(self): - return self._open_price + def duration(self): + if TradeStatus.CLOSED.equals(self.status): + return self.closed_at - self.opened_at - @property - def closed_price(self): - return self._closed_price + if self.opened_at is None: + return None - @property - def closed_at(self): - return self._closed_at + if self.updated_at is None: + return None - @property - def opened_at(self): - return self._opened_at + return self.updated_at - self.opened_at @property def size(self): return self.amount * self.open_price @property - def status(self): - return "CLOSED" if self.closed_at else "OPEN" + def change(self): + if TradeStatus.CLOSED.equals(self.status): + cost = self.amount * self.open_price + return self.net_gain - cost + + if self.last_reported_price is None: + return 0 + + cost = self.remaining * self.open_price + gain = (self.remaining * self.last_reported_price) - cost + gain += self.net_gain + return gain @property - def net_gain(self): + def net_gain_absolute(self): - if self.closed_at is None: - return 0 + if TradeStatus.CLOSED.equals(self.status): + return self.net_gain + else: + gain = 0 - return self.amount * (self.closed_price - self.open_price) + if self.last_reported_price is not None: + gain = ( + self.remaining * + (self.last_reported_price - self.open_price) + ) + + gain += self.net_gain + return gain @property def net_gain_percentage(self): - if self.closed_at is None: - return 0 + if TradeStatus.CLOSED.equals(self.status): + return (self.net_gain / self.cost) * 100 - return self.net_gain / self.size * 100 + else: + gain = 0 - @property - def duration(self): - closed_at = self.closed_at + if self.last_reported_price is not None: + gain = ( + self.remaining * + (self.last_reported_price - self.open_price) + ) + + gain += self.net_gain - if closed_at is None: - closed_at = datetime.utcnow() + if self.cost != 0: + return (gain / self.cost) * 100 - return (closed_at - self.opened_at).total_seconds() / 3600 + return 0 @property - def current_price(self): - return self._current_price + def percentage_change(self): - @current_price.setter - def current_price(self, current_price): - self._current_price = current_price + if TradeStatus.CLOSED.equals(self.status): + cost = self.amount * self.open_price + gain = self.net_gain - cost + return (gain / cost) * 100 - @property - def value(self): + if self.last_reported_price is None: + return 0 - if self.closed_at is not None: - return self.amount * self.closed_price + cost = self.remaining * self.open_price + gain = (self.remaining * self.last_reported_price) - cost + gain += self.net_gain + return (gain / cost) * 100 - if self.current_price is None: - return None + def is_stop_loss_triggered(self): - return self.amount * self.current_price + if self.stop_loss_percentage is None: + return False - @property - def percentage_change(self): + if self.last_reported_price is None: + return False - if self.closed_at is not None or self.value is None: - return 0 - value = self.value - return (value - self.size) / self.size * 100 + if self.open_price is None: + return False - def get_percentage_change(self): - return self.percentage_change + stop_loss_price = self.open_price * \ + (1 - (self.stop_loss_percentage / 100)) - @property - def absolute_change(self): + return self.last_reported_price <= stop_loss_price - if self.closed_at is None: - return 0 + def is_trailing_stop_loss_triggered(self): - value = self.value - return value - self.size + if self.trailing_stop_loss_percentage is None: + return False - def get_absolute_change(self): - return self.absolute_change + if self.last_reported_price is None: + return False - def is_manual_stop_loss_trigger( - self, - current_price, - stop_loss_percentage, - prices: List[float] = None, - ohlcv_df: DataFrame = None - ): - """ - Function to check if the stop loss is triggered for a given trade. - - You can use either the prices list or the ohlcv_df DataFrame to - calculate the stop loss. The dataframe needs to be a Polars - DataFrame with the following columns: "Datetime" and "Close". - - You can use the default CCXTOHLCVMarketDataSource to get the ohlcv_df - DataFrame. - - Stop loss is triggered when the current price is lower than the - calculated stop loss price. The stop loss price is calculated by - taking the highest price of the given range. If the highest price - is lower than the open price, the stop loss price is calculated by - taking the open price and subtracting the stop loss percentage. - If the highest price is higher than the open price, the stop loss - price is calculated by taking the open price and adding the stop - loss percentage. - """ - - if prices is None and ohlcv_df is None: - raise OperationalException( - "Either prices or a polars ohlcv dataframe must be provided" - ) - - if current_price < self.open_price: - stop_loss_price = self.open_price * \ - (1 - stop_loss_percentage / 100) - return current_price <= stop_loss_price + if self.high_water_mark is None: + + if self.open_price is not None: + self.high_water_mark = self.open_price + else: + return False + + stop_loss_price = self.high_water_mark * \ + (1 - (self.trailing_stop_loss_percentage / 100)) + + return self.last_reported_price <= stop_loss_price + + def to_dict(self, datetime_format=None): + + if datetime_format is not None: + opened_at = self.opened_at.strftime(datetime_format) \ + if self.opened_at else None + closed_at = self.closed_at.strftime(datetime_format) \ + if self.closed_at else None + updated_at = self.updated_at.strftime(datetime_format) \ + if self.updated_at else None else: - # If dataframes are provided, we use the dataframe to calculate - # the stop loss price - if ohlcv_df is not None: - column_type = ohlcv_df['Datetime'].dtype - - if isinstance(column_type, pl.Datetime): - filtered_df = ohlcv_df.filter( - pl.col('Datetime') >= self.opened_at - ) - else: - filtered_df = ohlcv_df.filter( - pl.col('Datetime') >= self.opened_at.strftime( - DATETIME_FORMAT - ) - ) - - prices = filtered_df['Close'].to_numpy() - - highest_price = max(prices) - stop_loss_price = highest_price * (1 - stop_loss_percentage / 100) - return current_price <= stop_loss_price - - def to_dict(self): + opened_at = self.opened_at + closed_at = self.closed_at + updated_at = self.updated_at + return { - "buy_order_id": self.buy_order_id, - "sell_order_id": self.sell_order_id, + "id": self.id, + "orders": [ + order.to_dict(datetime_format=datetime_format) + for order in self.orders + ], "target_symbol": self.target_symbol, "trading_symbol": self.trading_symbol, "status": self.status, "amount": self.amount, + "remaining": self.remaining, "open_price": self.open_price, - "current_price": self.current_price, - "closed_price": self.closed_price, - "opened_at": self.opened_at.strftime(DATETIME_FORMAT) - if self.opened_at else None, - "closed_at": self.closed_at.strftime(DATETIME_FORMAT) - if self.closed_at else None, - "change": self.percentage_change, - "absolute_change": self.absolute_change, + "last_reported_price": self.last_reported_price, + "opened_at": opened_at, + "closed_at": closed_at, + "updated_at": updated_at, + "net_gain": self.net_gain } @staticmethod def from_dict(data): return Trade( - buy_order_id=data["buy_order_id"] if "buy_order_id" - in data else None, - sell_order_id=data["sell_order_id"] if "sell_order_id" - in data else None, + id=data.get("id", None), + orders=data.get("orders", None), target_symbol=data["target_symbol"], trading_symbol=data["trading_symbol"], amount=data["amount"], open_price=data["open_price"], - opened_at=datetime.strptime( - data["opened_at"], DATETIME_FORMAT - ), - closed_price=data["closed_price"], - closed_at=datetime.strptime( - data["closed_at"], DATETIME_FORMAT - ) if data["closed_at"] else None, - current_price=data["current_price"], + opened_at=data["opened_at"], + closed_at=data["closed_at"], + remaining=data.get("remaining", 0), + net_gain=data.get("net_gain", 0), + last_reported_price=data.get("last_reported_price"), + status=data["status"], + cost=data.get("cost", 0), + updated_at=data.get("updated_at"), ) def __repr__(self): return self.repr( + id=self.id, target_symbol=self.target_symbol, trading_symbol=self.trading_symbol, status=self.status, amount=self.amount, + remaining=self.remaining, open_price=self.open_price, - current_price=self.current_price, - closed_price=self.closed_price, opened_at=self.opened_at, closed_at=self.closed_at, - value=self.value, - change=self.percentage_change, - absolute_change=self.absolute_change, + net_gain=self.net_gain, + last_reported_price=self.last_reported_price, ) + + def __lt__(self, other): + # Define the less-than comparison based on created_at attribute + return self.opened_at < other.opened_at diff --git a/investing_algorithm_framework/domain/models/trade/trade_status.py b/investing_algorithm_framework/domain/models/trade/trade_status.py index 16afbf37..4df3509e 100644 --- a/investing_algorithm_framework/domain/models/trade/trade_status.py +++ b/investing_algorithm_framework/domain/models/trade/trade_status.py @@ -2,6 +2,7 @@ class TradeStatus(Enum): + CREATED = "CREATED" OPEN = "OPEN" CLOSED = "CLOSED" diff --git a/investing_algorithm_framework/domain/services/market_data_sources.py b/investing_algorithm_framework/domain/services/market_data_sources.py index b575e025..73cf140b 100644 --- a/investing_algorithm_framework/domain/services/market_data_sources.py +++ b/investing_algorithm_framework/domain/services/market_data_sources.py @@ -91,21 +91,30 @@ def prepare_data( config, backtest_start_date, backtest_end_date, - **kwargs ): + """ + Function to prepare the data for the backtest. + This function needs to be implemented by the child class. + + Args: + config: dict - the configuration of the application + backtest_start_date: datetime - the start date of the backtest + backtest_end_date: datetime - the end date of the backtest + + Returns: + None + """ pass @abstractmethod - def get_data(self, **kwargs): + def get_data(self, date, config): """ - Get data from the market data source. - :param kwargs: Additional arguments to get the data. Common arguments - - start_date: datetime - - end_date: datetime - - time_frame: str - - backtest_start_date: datetime - - backtest_end_date: datetime - - backtest_data_index_date: datetime + Function to get the data for the backtest. This function needs to be + implemented by the child class. + + Args: + date: datetime - the date for which the data is required + config: dict - the configuration of the application """ pass @@ -222,15 +231,22 @@ def get_storage_path(self): return self.storage_path @abstractmethod - def get_data(self, **kwargs): + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): """ Get data from the market data source. - :param kwargs: Additional arguments to get the data. Common arguments - - start_date: datetime - - end_date: datetime - - time_frame: str - :return: Object with the data + Args: + config (dict): the configuration of the application + start_date (optional) (datetime): the start date of the data + end_date (optional) (datetime): the end date of the data + + Returns: + DataFrame: the data from the market data source """ pass diff --git a/investing_algorithm_framework/domain/utils/backtesting.py b/investing_algorithm_framework/domain/utils/backtesting.py index 7c051a7f..4bd2edd4 100644 --- a/investing_algorithm_framework/domain/utils/backtesting.py +++ b/investing_algorithm_framework/domain/utils/backtesting.py @@ -7,7 +7,7 @@ from tabulate import tabulate from investing_algorithm_framework.domain import DATETIME_FORMAT, \ - BacktestDateRange + BacktestDateRange, TradeStatus from investing_algorithm_framework.domain.exceptions import \ OperationalException from investing_algorithm_framework.domain.models.backtesting import \ @@ -49,6 +49,10 @@ def pretty_print_profit_evaluation(reports, precision=4): profit_table["Profit percentage"] = [ f"{report.total_net_gain_percentage:.{precision}f}%" for report in reports ] + profit_table["Percentage positive trades"] = [ + f"{report.percentage_positive_trades:.{0}f}%" + for report in reports + ] profit_table["Date range"] = [ f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}" for report in reports @@ -70,6 +74,10 @@ def pretty_print_growth_evaluation(reports, precision=4): growth_table["Growth percentage"] = [ f"{report.growth_rate:.{precision}f}%" for report in reports ] + growth_table["Percentage positive trades"] = [ + f"{report.percentage_positive_trades:.{0}f}%" + for report in reports + ] growth_table["Date range"] = [ f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}" for report in reports @@ -243,6 +251,42 @@ def pretty_print_percentage_positive_trades( print(f"{COLOR_YELLOW}Most positive trades:{COLOR_RESET} {COLOR_GREEN}Algorithm {percentages.name} {percentages.percentage_positive_trades:.{precision}f}%{COLOR_RESET}") +def pretty_print_trades(backtest_report, precision=4): + print(f"{COLOR_YELLOW}Trades overview{COLOR_RESET}") + trades_table = {} + trades_table["Pair"] = [ + f"{trade.target_symbol}-{trade.trading_symbol}" + for trade in backtest_report.trades + ] + trades_table["Open date"] = [ + trade.opened_at for trade in backtest_report.trades + ] + trades_table["Close date"] = [ + trade.closed_at for trade in backtest_report.trades + ] + trades_table["Duration (hours)"] = [ + trade.duration for trade in backtest_report.trades + ] + trades_table[f"Size ({backtest_report.trading_symbol})"] = [ + f"{trade.size:.{precision}f}" for trade in backtest_report.trades + ] + trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [ + f"{trade.net_gain:.{precision}f}" + (" (unrealized)" if trade.closed_price is None else "") + for trade in backtest_report.trades + ] + trades_table["Net gain percentage"] = [ + f"{trade.net_gain_percentage:.{precision}f}%" + (" (unrealized)" if trade.closed_price is None else "") + for trade in backtest_report.trades + ] + trades_table[f"Open price ({backtest_report.trading_symbol})"] = [ + trade.open_price for trade in backtest_report.trades + ] + trades_table[f"Close price ({backtest_report.trading_symbol})"] = [ + trade.closed_price for trade in backtest_report.trades + ] + print(tabulate(trades_table, headers="keys", tablefmt="rounded_grid")) + + def pretty_print_backtest_reports_evaluation( backtest_reports_evaluation: BacktestReportsEvaluation, precision=4, @@ -412,22 +456,33 @@ def pretty_print_backtest( trades_table["Duration (hours)"] = [ trade.duration for trade in backtest_report.trades ] - trades_table[f"Size ({backtest_report.trading_symbol})"] = [ - f"{trade.size:.{precision}f}" for trade in backtest_report.trades + trades_table[f"Cost ({backtest_report.trading_symbol})"] = [ + f"{trade.cost:.{precision}f}" for trade in backtest_report.trades ] trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [ f"{trade.net_gain:.{precision}f}" for trade in backtest_report.trades ] + + # Add (unrealized) to the net gain if the trade is still open + trades_table[f"Net gain ({backtest_report.trading_symbol})"] = [ + f"{trade.net_gain_absolute:.{precision}f}" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "") + for trade in backtest_report.trades + ] trades_table["Net gain percentage"] = [ - f"{trade.net_gain_percentage:.{precision}f}%" + f"{trade.net_gain_percentage:.{precision}f}%" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "") for trade in backtest_report.trades ] trades_table[f"Open price ({backtest_report.trading_symbol})"] = [ trade.open_price for trade in backtest_report.trades ] - trades_table[f"Close price ({backtest_report.trading_symbol})"] = [ - trade.closed_price for trade in backtest_report.trades + trades_table[ + f"Last reported price ({backtest_report.trading_symbol})" + ] = [ + trade.last_reported_price for trade in backtest_report.trades + ] + trades_table["Stop loss triggered"] = [ + trade.stop_loss_triggered for trade in backtest_report.trades ] print(tabulate(trades_table, headers="keys", tablefmt="rounded_grid")) diff --git a/investing_algorithm_framework/domain/utils/polars.py b/investing_algorithm_framework/domain/utils/polars.py index 20a13cb8..5398aebb 100644 --- a/investing_algorithm_framework/domain/utils/polars.py +++ b/investing_algorithm_framework/domain/utils/polars.py @@ -25,6 +25,10 @@ def convert_polars_to_pandas( DataFrame - Pandas DataFrame that has been converted from a Polars DataFrame """ + + if not isinstance(data, PolarsDataFrame): + raise ValueError("Data must be a Polars DataFrame") + data = data.to_pandas().copy() if add_index: diff --git a/investing_algorithm_framework/indicators/trend.py b/investing_algorithm_framework/indicators/trend.py index 580fdd16..34430994 100644 --- a/investing_algorithm_framework/indicators/trend.py +++ b/investing_algorithm_framework/indicators/trend.py @@ -41,8 +41,14 @@ def is_uptrend( if isinstance(data, pd.Series): # Check if the data keys are present in the data - if fast_column not in data.index or slow_column not in data.index: - raise OperationalException("Data keys not present in the data.") + if fast_column not in data.index: + raise OperationalException( + f"Data column {fast_column} not present in the data." + ) + if slow_column not in data.index: + raise OperationalException( + f"Data columns {slow_column} not present in the data." + ) return data[fast_column] > data[slow_column] @@ -362,6 +368,12 @@ def get_ema( named 'EMA_{period}' or named according to the result_column_name """ + + if source_column_name not in data.columns: + raise OperationalException( + f"Source column {source_column_name} not present in the data." + ) + ema = tp.ema(data[source_column_name].to_numpy(), period=period) if result_column_name: diff --git a/investing_algorithm_framework/infrastructure/__init__.py b/investing_algorithm_framework/infrastructure/__init__.py index cc6be269..0c29345c 100644 --- a/investing_algorithm_framework/infrastructure/__init__.py +++ b/investing_algorithm_framework/infrastructure/__init__.py @@ -1,12 +1,12 @@ from .database import setup_sqlalchemy, Session, \ create_all_tables from .models import SQLPortfolio, SQLOrder, SQLPosition, \ - SQLPortfolioSnapshot, SQLPositionSnapshot, \ + SQLPortfolioSnapshot, SQLPositionSnapshot, SQLTrade, \ CCXTOHLCVBacktestMarketDataSource, CCXTOrderBookMarketDataSource, \ CCXTTickerMarketDataSource, CCXTOHLCVMarketDataSource, \ CSVOHLCVMarketDataSource, CSVTickerMarketDataSource from .repositories import SQLOrderRepository, SQLPositionRepository, \ - SQLPortfolioRepository, \ + SQLPortfolioRepository, SQLTradeRepository, \ SQLPortfolioSnapshotRepository, SQLPositionSnapshotRepository from .services import PerformanceService, CCXTMarketService, \ AzureBlobStorageStateHandler @@ -21,6 +21,7 @@ "setup_sqlalchemy", "Session", "SQLPortfolio", + "SQLTrade", "SQLOrder", "SQLPosition", "PerformanceService", @@ -35,5 +36,6 @@ "CSVTickerMarketDataSource", "CCXTOHLCVBacktestMarketDataSource", "CCXTOrderBookMarketDataSource", - "AzureBlobStorageStateHandler" + "AzureBlobStorageStateHandler", + "SQLTradeRepository" ] diff --git a/investing_algorithm_framework/infrastructure/models/__init__.py b/investing_algorithm_framework/infrastructure/models/__init__.py index a11c53f8..376a802c 100644 --- a/investing_algorithm_framework/infrastructure/models/__init__.py +++ b/investing_algorithm_framework/infrastructure/models/__init__.py @@ -5,6 +5,7 @@ from .order import SQLOrder from .portfolio import SQLPortfolio, SQLPortfolioSnapshot from .position import SQLPosition, SQLPositionSnapshot +from .trade import SQLTrade __all__ = [ "SQLOrder", @@ -18,4 +19,5 @@ "CCXTOHLCVMarketDataSource", "CSVTickerMarketDataSource", "CSVOHLCVMarketDataSource", + "SQLTrade" ] diff --git a/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py b/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py index 40b91778..e584168d 100644 --- a/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +++ b/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py @@ -180,39 +180,27 @@ def _create_file_path(self): ) ) - def get_data(self, **kwargs): + def get_data( + self, + date, + config=None, + ): """ Get data implementation of ccxt based ohlcv backtest market data source. This implementation will use polars to load and filter the data. """ - start_date = kwargs.get("start_date") - end_date = kwargs.get("end_date") - backtest_index_date = kwargs.get("backtest_index_date") - if self.data is None: self.load_data() - if start_date is None \ - and end_date is None \ - and backtest_index_date is None: - return self.data + end_date = date - if backtest_index_date is not None: - end_date = backtest_index_date - start_date = self.create_start_date( - end_date, self.time_frame, self.window_size - ) - else: - if start_date is None: - start_date = self.create_start_date( - end_date, self.time_frame, self.window_size - ) + if end_date is None: + return self.data - if end_date is None: - end_date = self.create_end_date( - start_date, self.time_frame, self.window_size - ) + start_date = self.create_start_date( + end_date, self.time_frame, self.window_size + ) if start_date < self._start_date_data_source: raise OperationalException( @@ -233,7 +221,6 @@ def get_data(self, **kwargs): (self.data['Datetime'] >= start_date.strftime(DATETIME_FORMAT)) & (self.data['Datetime'] <= end_date.strftime(DATETIME_FORMAT)) ) - return selection def to_backtest_market_data_source(self) -> BacktestMarketDataSource: @@ -301,7 +288,6 @@ def prepare_data( config, backtest_start_date, backtest_end_date, - **kwargs ): """ Prepare data implementation of ccxt based ticker backtest market @@ -313,7 +299,6 @@ def prepare_data( When downloading the data it will use the ccxt library. """ - config = self.config total_minutes = TimeFrame.from_string(self.time_frame)\ .amount_of_minutes self.backtest_data_start_date = \ @@ -395,30 +380,27 @@ def to_backtest_market_data_source(self) -> BacktestMarketDataSource: def empty(self): return False - def get_data(self, **kwargs): + def get_data( + self, + date, + config, + ): """ Get data implementation of ccxt based ticker backtest market data source """ - if "backtest_index_date" not in kwargs: - raise OperationalException( - "backtest_index_date should be passed as a parameter " - "for CCXTTickerBacktestMarketDataSource" - ) - file_path = self._create_file_path() - backtest_index_date = kwargs["backtest_index_date"] # Filter the data based on the backtest index date and the end date df = polars.read_csv(file_path) filtered_df = df.filter( - (df['Datetime'] >= backtest_index_date.strftime(DATETIME_FORMAT)) + (df['Datetime'] >= date.strftime(DATETIME_FORMAT)) ) # If nothing is found, get all dates before the index date if len(filtered_df) == 0: filtered_df = df.filter( - (df['Datetime'] <= backtest_index_date.strftime( + (df['Datetime'] <= date.strftime( DATETIME_FORMAT)) ) first_row = filtered_df.tail(1)[0] @@ -447,20 +429,23 @@ class CCXTOHLCVMarketDataSource(OHLCVMarketDataSource): ccxt to download all ohlcv data sources. """ - def get_data(self, **kwargs): + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): """ Implementation of get_data for CCXTOHLCVMarketDataSource. This implementation uses the CCXTMarketService to get the OHLCV data. Args: - window_size: int (optional) - the total amount of candle - sticks that need to be returned start_date: datetime (optional) - the start date of the data. The first candle stick should close to this date. end_date: datetime (optional) - the end date of the data. The last candle stick should close to this date. storage_path: string (optional) - the storage path specifies the - directory where the data is written to or read from. + directory where the data is written to or read from. If set the data provider will write all its downloaded data to this location. Also, it will check if the data already exists at the storage location. If this is the @@ -477,38 +462,9 @@ def get_data(self, **kwargs): if self.config is not None: market_service.config = self.config - if "window_size" in kwargs: - self.window_size = kwargs["window_size"] - - start_date = None - end_date = None - - if "start_date" in kwargs: - start_date = kwargs["start_date"] - - if not isinstance(start_date, datetime): - raise OperationalException( - "start_date should be a datetime object" - ) - - if "end_date" in kwargs: - end_date = kwargs["end_date"] - - if not isinstance(end_date, datetime): - raise OperationalException( - "end_date should be a datetime object" - ) - # Calculate the start and end dates if start_date is None or end_date is None: - if self.window_size is None: - raise OperationalException( - "Either end_date or window_size " - "should be passed as a " - "parameter for CCXTOHLCVMarketDataSource" - ) - if start_date is None: if end_date is None: @@ -526,10 +482,7 @@ def get_data(self, **kwargs): window_size=self.window_size ) - if "storage_path" in kwargs: - storage_path = kwargs["storage_path"] - else: - storage_path = self.get_storage_path() + storage_path = self.get_storage_path() logger.info( f"Getting OHLCV data for {self.symbol} " + @@ -698,7 +651,12 @@ def write_data_to_storage( class CCXTOrderBookMarketDataSource(OrderBookMarketDataSource): - def get_data(self, **kwargs): + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): market_service = CCXTMarketService( market_credential_service=self.market_credential_service ) @@ -727,37 +685,20 @@ def __init__( ) self._backtest_time_frame = backtest_time_frame - def get_data(self, **kwargs): + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): market_service = CCXTMarketService( market_credential_service=self.market_credential_service ) market_service.config = self.config - - if self.market is None: - - if "market" not in kwargs: - raise OperationalException( - "Either market or market should be " - "passed as a parameter" - ) - else: - market = kwargs["market"] - else: - market = self.market + market = self.market market_service.market = market - - if self.symbol is None: - - if "symbol" not in kwargs: - raise OperationalException( - "Either symbol or symbol should be passed as a parameter" - ) - else: - symbol = kwargs["symbol"] - else: - symbol = self.symbol - + symbol = self.symbol return market_service.get_ticker(symbol=symbol, market=market) def to_backtest_market_data_source(self) -> BacktestMarketDataSource: diff --git a/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py b/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py index a21ae2da..2c5bf66c 100644 --- a/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +++ b/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py @@ -17,28 +17,23 @@ class CSVOHLCVMarketDataSource(OHLCVMarketDataSource): from a csv file. Market data source that reads OHLCV data from a csv file. """ - def empty(self, start_date, end_date=None): - if end_date is None: - end_date = self.create_end_date( - start_date, self.time_frame, self.window_size - ) - data = self.get_data(start_date=start_date, end_date=end_date) + def empty(self, end_date): + data = self.get_data(end_date=end_date, config={}) return len(data) == 0 def __init__( self, csv_file_path, - identifier=None, market=None, symbol=None, - time_frame=None, + identifier=None, window_size=None, ): super().__init__( identifier=identifier, market=market, symbol=symbol, - time_frame=time_frame, + time_frame=None, window_size=window_size, ) self._csv_file_path = csv_file_path @@ -67,7 +62,12 @@ def __init__( def csv_file_path(self): return self._csv_file_path - def get_data(self, **kwargs): + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): """ Get the data from the csv file. The data can be filtered by the start_date and end_date in the kwargs. backtest_index_date @@ -84,60 +84,70 @@ def get_data(self, **kwargs): Returns: df (polars.DataFrame): The data from the csv file. """ - start_date = kwargs.get("start_date") - end_date = kwargs.get("end_date") - backtest_index_date = kwargs.get("backtest_index_date") - - if "window_size" in kwargs: - self.window_size = kwargs["window_size"] - if start_date is None \ - and end_date is None \ - and backtest_index_date is None: + if start_date is None and end_date is None: return polars.read_csv( self.csv_file_path, columns=self._columns, separator="," ) - if backtest_index_date is not None: - end_date = backtest_index_date + if end_date is not None and start_date is not None: - if self.window_size is None: + if end_date < start_date: raise OperationalException( - "Either end_date or window_size " - "should be passed as a " - "parameter for CCXTOHLCVMarketDataSource" + f"End date {end_date} is before the start date " + f"{start_date}" ) - start_date = self.create_start_date( - end_date, self.time_frame, self.window_size + if start_date > self._end_date_data_source: + return polars.DataFrame() + + df = polars.read_csv( + self.csv_file_path, columns=self._columns, separator="," + ) + + df = df.filter( + (df['Datetime'] >= start_date.strftime(DATETIME_FORMAT)) + & (df['Datetime'] <= end_date.strftime(DATETIME_FORMAT)) ) - else: - if start_date is None: - - if self.window_size is None: - raise OperationalException( - "Either end_date or window_size " - "should be passed as a " - "parameter for CCXTOHLCVMarketDataSource" - ) - - start_date = self.create_start_date( - end_date, self.time_frame, self.window_size - ) + return df - if end_date is None: - end_date = self.create_end_date( - start_date, self.time_frame, self.window_size - ) + if start_date is not None: + + if start_date < self._start_date_data_source: + return polars.DataFrame() - df = polars.read_csv( + if start_date > self._end_date_data_source: + return polars.DataFrame() + + df = polars.read_csv( + self.csv_file_path, columns=self._columns, separator="," + ) + df = df.filter( + (df['Datetime'] >= start_date.strftime(DATETIME_FORMAT)) + ) + df = df.head(self.window_size) + return df + + if end_date is not None: + + if end_date < self._start_date_data_source: + return polars.DataFrame() + + if end_date > self._end_date_data_source: + return polars.DataFrame() + + df = polars.read_csv( + self.csv_file_path, columns=self._columns, separator="," + ) + df = df.filter( + (df['Datetime'] <= end_date.strftime(DATETIME_FORMAT)) + ) + df = df.tail(self.window_size) + return df + + return polars.read_csv( self.csv_file_path, columns=self._columns, separator="," ) - df = df.filter( - (df['Datetime'] >= start_date.strftime(DATETIME_FORMAT)) - & (df['Datetime'] <= end_date.strftime(DATETIME_FORMAT)) - ) - return df def dataframe_to_list_of_lists(self, dataframe, columns): # Extract selected columns from DataFrame and convert @@ -188,21 +198,18 @@ def __init__( def csv_file_path(self): return self._csv_file_path - def get_data(self, **kwargs): - date = None - - if "index_datetime" in kwargs: - date = kwargs["index_datetime"] - - if "start_date" in kwargs: - date = kwargs["start_date"] - - if 'date' in kwargs: - date = kwargs['date'] + def get_data( + self, + start_date: datetime = None, + end_date: datetime = None, + config=None, + ): - if date is None: + if end_date is None: raise OperationalException("Date is required to get ticker data") + date = end_date + if not isinstance(date, datetime): if isinstance(date, str): diff --git a/investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py b/investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py index caf2a57c..47b5738e 100644 --- a/investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +++ b/investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py @@ -1,4 +1,3 @@ -from datetime import datetime from pandas import DataFrame from investing_algorithm_framework.domain import OHLCVMarketDataSource, \ @@ -41,7 +40,7 @@ def prepare_data(self, config, backtest_start_date, backtest_end_date, def empty(self): pass - def get_data(self, **kwargs): + def get_data(self, config, date): pass def _validate_dataframe_with_ohlcv_structure(self): @@ -90,7 +89,7 @@ def __init__( self.dataframe = dataframe self._validate_dataframe_with_ohlcv_structure() - def get_data(self, **kwargs): + def get_data(self, config, date): """ Implementation of get_data for PandasOHLCVMarketDataSource. This implementation uses the dataframe provided in the constructor. @@ -108,49 +107,12 @@ def get_data(self, **kwargs): returns: Polars Dataframe: a polars.DataFrame with the OHLCV data """ - - if "window_size" in kwargs: - self.window_size = kwargs["window_size"] - - if "start_date" in kwargs: - start_date = kwargs["start_date"] - - if not isinstance(start_date, datetime): - raise OperationalException( - "start_date should be a datetime object" - ) - else: - raise OperationalException( - "start_date should be set for CCXTOHLCVMarketDataSource" - ) - - if "end_date" not in kwargs: - - if self.window_size is None: - raise OperationalException( - "Either end_date or window_size " - "should be passed as a " - "parameter for CCXTOHLCVMarketDataSource" - ) - - end_date = self.create_end_date( - start_date, self.time_frame, self.window_size - ) - else: - end_date = kwargs["end_date"] - - if not isinstance(end_date, datetime): - raise OperationalException( - "end_date should be a datetime object" - ) - - if not isinstance(start_date, datetime): - raise OperationalException( - "start_date should be a datetime object" - ) + end_date = self.create_end_date( + date, self.time_frame, self.window_size + ) # Slice the pandas dataframe object - return self.dataframe["Datetime" >= start_date] + return self.dataframe["Datetime" >= date & "Datetime" <= end_date] def to_backtest_market_data_source(self) -> BacktestMarketDataSource: # return CCXTOHLCVBacktestMarketDataSource( diff --git a/investing_algorithm_framework/infrastructure/models/order/order.py b/investing_algorithm_framework/infrastructure/models/order/order.py index 7ee3fc13..aeda00cd 100644 --- a/investing_algorithm_framework/infrastructure/models/order/order.py +++ b/investing_algorithm_framework/infrastructure/models/order/order.py @@ -9,6 +9,8 @@ from investing_algorithm_framework.infrastructure.database import SQLBaseModel from investing_algorithm_framework.infrastructure.models.model_extension \ import SQLAlchemyModelExtension +from investing_algorithm_framework.infrastructure.models.\ + order_trade_association import order_trade_association logger = logging.getLogger("investing_algorithm_framework") @@ -21,24 +23,22 @@ class SQLOrder(Order, SQLBaseModel, SQLAlchemyModelExtension): trading_symbol = Column(String) order_side = Column(String, nullable=False, default=OrderSide.BUY.value) order_type = Column(String, nullable=False, default=OrderType.LIMIT.value) + trades = relationship( + 'SQLTrade', secondary=order_trade_association, back_populates='orders' + ) price = Column(Float) amount = Column(Float) - filled = Column(Float) - remaining = Column(Float) - cost = Column(Float) + remaining = Column(Float, default=0) + filled = Column(Float, default=0) + cost = Column(Float, default=0) status = Column(String) position_id = Column(Integer, ForeignKey('positions.id')) position = relationship("SQLPosition", back_populates="orders") created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow) - trade_closed_at = Column(DateTime, default=None) - trade_closed_price = Column(Float, default=None) - trade_closed_amount = Column(Float, default=None) - net_gain = Column(Float, default=0) order_fee = Column(Float, default=None) order_fee_currency = Column(String) order_fee_rate = Column(Float, default=None) - _available_amount = None def update(self, data): @@ -50,27 +50,13 @@ def update(self, data): price = data.pop('price') self.price = price - if 'filled' in data and data['filled'] is not None: - filled = data.pop('filled') - self.filled = filled - if 'remaining' in data and data['remaining'] is not None: remaining = data.pop('remaining') self.remaining = remaining - if 'net_gain' in data and data['net_gain'] is not None: - net_gain = data.pop('net_gain') - self.net_gain = net_gain - - if 'trade_closed_price' in data and \ - data['trade_closed_price'] is not None: - trade_closed_price = data.pop('trade_closed_price') - self.trade_closed_price = trade_closed_price - - if 'trade_closed_amount' in data and \ - data['trade_closed_amount'] is not None: - trade_closed_amount = data.pop('trade_closed_amount') - self.trade_closed_amount = trade_closed_amount + if 'filled' in data and data['filled'] is not None: + filled = data.pop('filled') + self.filled = filled if "status" in data and data["status"] is not None: self.status = OrderStatus.from_value(data.pop("status")).value @@ -82,6 +68,8 @@ def from_order(order): return SQLOrder( external_id=order.external_id, amount=order.get_amount(), + filled=order.get_filled(), + remaining=order.get_remaining(), price=order.price, order_type=order.get_order_type(), order_side=order.get(), @@ -90,17 +78,18 @@ def from_order(order): trading_symbol=order.get_trading_symbol(), created_at=order.get_created_at(), updated_at=order.get_updated_at(), - trade_closed_at=order.get_trade_closed_at(), - trade_closed_price=order.get_trade_closed_price(), - trade_closed_amount=order.get_trade_closed_amount(), - net_gain=order.get_net_gain(), ) @staticmethod def from_ccxt_order(ccxt_order): """ Create an Order object from a CCXT order object - :param ccxt_order: CCXT order object + + Args: + ccxt_order: CCXT order object + + Returns: + Order: Order object """ status = OrderStatus.from_value(ccxt_order["status"]) target_symbol = ccxt_order.get("symbol").split("/")[0] diff --git a/investing_algorithm_framework/infrastructure/models/order_trade_association.py b/investing_algorithm_framework/infrastructure/models/order_trade_association.py new file mode 100644 index 00000000..742c6bf9 --- /dev/null +++ b/investing_algorithm_framework/infrastructure/models/order_trade_association.py @@ -0,0 +1,10 @@ +from sqlalchemy import Table, Column, Integer, ForeignKey +from investing_algorithm_framework.infrastructure.database import SQLBaseModel + +# Association table +order_trade_association = Table( + 'order_trade', # Table name + SQLBaseModel.metadata, + Column('order_id', Integer, ForeignKey('orders.id'), primary_key=True), + Column('trade_id', Integer, ForeignKey('trades.id'), primary_key=True) +) diff --git a/investing_algorithm_framework/infrastructure/models/trade.py b/investing_algorithm_framework/infrastructure/models/trade.py new file mode 100644 index 00000000..0a738728 --- /dev/null +++ b/investing_algorithm_framework/infrastructure/models/trade.py @@ -0,0 +1,106 @@ +from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean +from sqlalchemy.orm import relationship + +from investing_algorithm_framework.domain import Trade, TradeStatus +from investing_algorithm_framework.infrastructure.database import SQLBaseModel +from investing_algorithm_framework.infrastructure.models.model_extension \ + import SQLAlchemyModelExtension +from investing_algorithm_framework.infrastructure.models\ + .order_trade_association import order_trade_association + + +class SQLTrade(Trade, SQLBaseModel, SQLAlchemyModelExtension): + """ + SQL Trade model + + A trade is a combination of a buy and sell order that has been opened or + closed. + + A trade is considered opened when a buy order is executed and there is + no corresponding sell order. A trade is considered closed when a sell + order is executed and the amount of the sell order is equal or larger + to the amount of the buy order. + + A single sell order can close multiple buy orders. Also, a single + buy order can be closed by multiple sell orders. + + Attributes: + * orders: str, the id of the buy order + * target_symbol: str, the target symbol of the trade + * trading_symbol: str, the trading symbol of the trade + * closed_at: datetime, the datetime when the trade was closed + * remaining: float, the remaining amount of the trade + * net_gain: float, the net gain of the trade + * last_reported_price: float, the last reported price of the trade + * created_at: datetime, the datetime when the trade was created + * updated_at: datetime, the datetime when the trade was last updated + * status: str, the status of the trade + * stop_loss_percentage: float, the stop loss percentage of the trade + * trailing_stop_loss_percentage: float, the trailing stop loss percentage + """ + + __tablename__ = "trades" + id = Column(Integer, primary_key=True, unique=True) + orders = relationship( + 'SQLOrder', + secondary=order_trade_association, + back_populates='trades', + lazy='joined' + ) + target_symbol = Column(String) + trading_symbol = Column(String) + closed_at = Column(DateTime, default=None) + opened_at = Column(DateTime, default=None) + open_price = Column(Float, default=None) + amount = Column(Float, default=None) + remaining = Column(Float, default=None) + net_gain = Column(Float, default=0) + cost = Column(Float, default=0) + last_reported_price = Column(Float, default=None) + high_water_mark = Column(Float, default=None) + updated_at = Column(DateTime, default=None) + status = Column(String, default=TradeStatus.CREATED.value) + stop_loss_percentage = Column(Float, default=None) + trailing_stop_loss_percentage = Column(Float, default=None) + stop_loss_triggered = Column(Boolean, default=False) + + def __init__( + self, + buy_order, + target_symbol, + trading_symbol, + opened_at, + amount, + remaining, + status=TradeStatus.CREATED.value, + closed_at=None, + updated_at=None, + net_gain=0, + cost=0, + last_reported_price=None, + high_water_mark=None, + sell_orders=[], + stop_loss_percentage=None, + trailing_stop_loss_percentage=None, + stop_loss_triggered=False + ): + self.orders = [buy_order] + self.open_price = buy_order.price + self.target_symbol = target_symbol + self.trading_symbol = trading_symbol + self.closed_at = closed_at + self.amount = amount + self.remaining = remaining + self.net_gain = net_gain + self.cost = cost + self.last_reported_price = last_reported_price + self.high_water_mark = high_water_mark + self.opened_at = opened_at + self.updated_at = updated_at + self.status = status + self.stop_loss_percentage = stop_loss_percentage + self.trailing_stop_loss_percentage = trailing_stop_loss_percentage + self.stop_loss_triggered = stop_loss_triggered + + if sell_orders is not None: + self.orders.extend(sell_orders) diff --git a/investing_algorithm_framework/infrastructure/repositories/__init__.py b/investing_algorithm_framework/infrastructure/repositories/__init__.py index eee27cc1..edd3d852 100644 --- a/investing_algorithm_framework/infrastructure/repositories/__init__.py +++ b/investing_algorithm_framework/infrastructure/repositories/__init__.py @@ -3,6 +3,7 @@ from .portfolio_snapshot_repository import SQLPortfolioSnapshotRepository from .position_repository import SQLPositionRepository from .position_snapshot_repository import SQLPositionSnapshotRepository +from .trade_repository import SQLTradeRepository __all__ = [ "SQLOrderRepository", @@ -10,4 +11,5 @@ "SQLPositionSnapshotRepository", "SQLPortfolioRepository", "SQLPortfolioSnapshotRepository", + "SQLTradeRepository" ] diff --git a/investing_algorithm_framework/infrastructure/repositories/position_repository.py b/investing_algorithm_framework/infrastructure/repositories/position_repository.py index 2d34177e..5d77bafd 100644 --- a/investing_algorithm_framework/infrastructure/repositories/position_repository.py +++ b/investing_algorithm_framework/infrastructure/repositories/position_repository.py @@ -20,6 +20,7 @@ def _apply_query_params(self, db, query, query_params): amount_lte_query_param = self.get_query_param( "amount_lte", query_params ) + order_id_query_param = self.get_query_param("order_id", query_params) if amount_query_param: query = query.filter( @@ -51,5 +52,11 @@ def _apply_query_params(self, db, query, query_params): query = query.filter( cast(SQLPosition.amount, Numeric) <= amount_lte_query_param ) + # Filter by order_id, orders is a one-to-many relationship + # with 3 position + if order_id_query_param: + query = query.filter( + SQLPosition.orders.any(id=order_id_query_param) + ) return query diff --git a/investing_algorithm_framework/infrastructure/repositories/repository.py b/investing_algorithm_framework/infrastructure/repositories/repository.py index 4cbaa3a1..c07a9ccb 100644 --- a/investing_algorithm_framework/infrastructure/repositories/repository.py +++ b/investing_algorithm_framework/infrastructure/repositories/repository.py @@ -150,6 +150,9 @@ def exists(self, query_params): def find(self, query_params): + if query_params is None or len(query_params) == 0: + raise ApiException("Find requires query parameters") + with Session() as db: try: query = db.query(self.base_class) diff --git a/investing_algorithm_framework/infrastructure/repositories/trade_repository.py b/investing_algorithm_framework/infrastructure/repositories/trade_repository.py new file mode 100644 index 00000000..f7ce184d --- /dev/null +++ b/investing_algorithm_framework/infrastructure/repositories/trade_repository.py @@ -0,0 +1,71 @@ +import logging +from sqlalchemy.exc import SQLAlchemyError + +from investing_algorithm_framework.domain import OrderStatus, ApiException +from investing_algorithm_framework.infrastructure.models import SQLPosition, \ + SQLPortfolio, SQLTrade, SQLOrder +from investing_algorithm_framework.infrastructure.database import Session + +from .repository import Repository + +logger = logging.getLogger("investing_algorithm_framework") + + +class SQLTradeRepository(Repository): + base_class = SQLTrade + DEFAULT_NOT_FOUND_MESSAGE = "The requested trade was not found" + + def _apply_query_params(self, db, query, query_params): + portfolio_query_param = self.get_query_param( + "portfolio_id", query_params + ) + status_query_param = self.get_query_param("status", query_params) + target_symbol = self.get_query_param( + "target_symbol", query_params + ) + trading_symbol = self.get_query_param("trading_symbol", query_params) + order_id_query_param = self.get_query_param("order_id", query_params) + + if order_id_query_param: + query = query.filter(SQLTrade.orders.any(id=order_id_query_param)) + + if portfolio_query_param is not None: + portfolio = db.query(SQLPortfolio).filter_by( + id=portfolio_query_param + ).first() + + if portfolio is None: + raise ApiException("Portfolio not found") + + # Query trades belonging to the portfolio + query = db.query(SQLTrade).join(SQLOrder, SQLTrade.orders) \ + .join(SQLPosition, SQLOrder.position_id == SQLPosition.id) \ + .filter(SQLPosition.portfolio_id == portfolio.id) + + if status_query_param: + status = OrderStatus.from_value(status_query_param) + # Explicitly filter on SQLTrade.status + query = query.filter(SQLTrade.status == status.value) + + if target_symbol: + # Explicitly filter on SQLTrade.target_symbol + query = query.filter(SQLTrade.target_symbol == target_symbol) + + if trading_symbol: + # Explicitly filter on SQLTrade.trading_symbol + query = query.filter(SQLTrade.trading_symbol == trading_symbol) + + return query + + def add_order_to_trade(self, trade, order): + with Session() as db: + try: + db.add(order) + db.add(trade) + trade.orders.append(order) + db.commit() + return trade + except SQLAlchemyError as e: + logger.error(f"Error saving trade: {e}") + db.rollback() + raise ApiException("Error saving trade") diff --git a/investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py b/investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py index 93ae5af3..cd5438bd 100644 --- a/investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +++ b/investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py @@ -1,6 +1,7 @@ import logging -from investing_algorithm_framework.domain import OrderStatus, OrderSide +from investing_algorithm_framework.domain import OrderStatus, OrderSide, \ + TradeStatus logger = logging.getLogger(__name__) @@ -15,10 +16,12 @@ def __init__( order_repository, position_repository, portfolio_repository, + trade_repository, ): self.order_repository = order_repository self.position_repository = position_repository self.portfolio_repository = portfolio_repository + self.trade_repository = trade_repository def get_total_net_gain(self, portfolio_id): pass @@ -43,20 +46,10 @@ def get_number_of_trades_closed(self, portfolio_id): return: The number of trades closed """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - number_of_trades_closed = 0 - orders = self.order_repository.get_all( - { - "portfolio_id": portfolio.id, - "order_side": OrderSide.BUY.value, - } + return self.trade_repository.count( + {"portfolio_id": portfolio.id, "status": TradeStatus.OPEN.value} ) - for order in orders: - if order.get_trade_closed_at() is not None: - number_of_trades_closed += 1 - - return number_of_trades_closed - def get_number_of_trades_open(self, portfolio_id): """ Get the number of trades open. This function will @@ -68,20 +61,10 @@ def get_number_of_trades_open(self, portfolio_id): return: The number of trades open """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - number_of_trades_open = 0 - orders = self.order_repository.get_all( - { - "portfolio_id": portfolio.id, - "order_side": OrderSide.BUY.value, - } + return self.trade_repository.count( + {"portfolio_id": portfolio.id, "status": TradeStatus.OPEN.value} ) - for order in orders: - if order.get_trade_closed_at() is None: - number_of_trades_open += 1 - - return number_of_trades_open - def get_percentage_positive_trades(self, portfolio_id): """ Get the percentage of positive trades. This function will @@ -95,19 +78,19 @@ def get_percentage_positive_trades(self, portfolio_id): return: The percentage of positive trades """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - orders = self.order_repository.get_all( + trades = self.trade_repository.get_all( {"portfolio_id": portfolio.id, "status": OrderStatus.CLOSED.value} ) - total_number_of_orders = len(orders) + total_number_of_trades = len(trades) - if total_number_of_orders == 0: + if total_number_of_trades == 0: return 0.0 - positive_orders = [ - order for order in orders if order.get_net_gain() > 0 + positive_trades = [ + trade for trade in trades if trade.net_gain > 0 ] - total_number_of_positive_orders = len(positive_orders) - return total_number_of_positive_orders / total_number_of_orders * 100 + total_number_of_positive_trades = len(positive_trades) + return total_number_of_positive_trades / total_number_of_trades * 100 def get_percentage_negative_trades(self, portfolio_id): """ @@ -123,19 +106,19 @@ def get_percentage_negative_trades(self, portfolio_id): """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - orders = self.order_repository.get_all( - {"portfolio_id": portfolio.id, "status": OrderStatus.CLOSED.value} + trades = self.trade_repository.get_all( + {"portfolio_id": portfolio.id, "status": TradeStatus.CLOSED.value} ) - total_number_of_orders = len(orders) + total_number_of_trades = len(trades) - if total_number_of_orders == 0: + if total_number_of_trades == 0: return 0.0 - negative_orders = [ - order for order in orders if order.get_net_gain() < 0 + negative_trades = [ + trade for trade in trades if trade.net_gain < 0 ] - total_number_of_negative_orders = len(negative_orders) - return total_number_of_negative_orders / total_number_of_orders * 100 + total_number_of_negative_trades = len(negative_trades) + return total_number_of_negative_trades / total_number_of_trades * 100 def get_growth_rate_of_backtest( self, portfolio_id, tickers, backtest_profile @@ -298,27 +281,20 @@ def get_average_trade_duration(self, portfolio_id): return: The average trade duration """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - buy_orders = self.order_repository.get_all( - { - "portfolio_id": portfolio.id, - "order_side": OrderSide.BUY.value, - } + trades = self.trade_repository.get_all( + {"portfolio_id": portfolio.id, "status": TradeStatus.CLOSED.value} ) - buy_orders_with_trade_closed = [ - order for order in buy_orders - if order.get_trade_closed_at() is not None - ] - if len(buy_orders_with_trade_closed) == 0: + if len(trades) == 0: return 0 total_duration = 0 - for order in buy_orders_with_trade_closed: - duration = order.get_trade_closed_at() - order.get_created_at() + for trade in trades: + duration = trade.closed_at - trade.opened_at total_duration += duration.total_seconds() / 3600 - return total_duration / len(buy_orders_with_trade_closed) + return total_duration / len(trades) def get_average_trade_size(self, portfolio_id): """ @@ -332,23 +308,15 @@ def get_average_trade_size(self, portfolio_id): return: The average trade size """ portfolio = self.portfolio_repository.find({"id": portfolio_id}) - buy_orders = self.order_repository.get_all( - { - "portfolio_id": portfolio.id, - "order_side": OrderSide.BUY.value, - } - ) - closed_buy_orders = [ - order for order in buy_orders - if order.get_trade_closed_at() is not None - ] + portfolio = self.portfolio_repository.find({"id": portfolio_id}) + trades = self.trade_repository.get_all({"portfolio_id": portfolio.id}) - if len(closed_buy_orders) == 0: + if len(trades) == 0: return 0 total_size = 0 - for order in closed_buy_orders: - total_size += order.get_amount() * order.get_price() + for trade in trades: + total_size += trade.amount * trade.open_price - return total_size / len(closed_buy_orders) + return total_size / len(trades) diff --git a/investing_algorithm_framework/services/backtesting/backtest_service.py b/investing_algorithm_framework/services/backtesting/backtest_service.py index 874acc7d..1377890d 100644 --- a/investing_algorithm_framework/services/backtesting/backtest_service.py +++ b/investing_algorithm_framework/services/backtesting/backtest_service.py @@ -57,7 +57,8 @@ def __init__( position_repository, performance_service, configuration_service, - portfolio_configuration_service + portfolio_configuration_service, + strategy_orchestrator_service, ): self._resource_directory = None self._order_service = order_service @@ -73,6 +74,7 @@ def __init__( self._backtest_market_data_sources = [] self._configuration_service = configuration_service self._portfolio_configuration_service = portfolio_configuration_service + self._strategy_orchestrator_service = strategy_orchestrator_service @property def resource_directory(self): @@ -98,8 +100,6 @@ def run_backtest( Also, all backtest data is downloaded (if not already downloaded) and the backtest is run for each date in the schedule. - At the end of the run all traces - Args: algorithm: The algorithm to run the backtest for backtest_date_range: The backtest date range @@ -163,10 +163,17 @@ def run_backtest( strategy_profiles, row['id'] ) index_date = parser.parse(str(index)) - self.run_backtest_for_profile( + self._configuration_service.add_value( + BACKTESTING_INDEX_DATETIME, index_date + ) + # self.run_backtest_for_profile( + # algorithm=algorithm, + # strategy=algorithm.get_strategy(strategy_profile.strategy_id), + # index_date=index_date, + # ) + self.run_backtest_v2( algorithm=algorithm, - strategy=algorithm.get_strategy(strategy_profile.strategy_id), - index_date=index_date, + strategy=algorithm.get_strategy(strategy_profile.strategy_id) ) report = self.create_backtest_report( @@ -239,6 +246,12 @@ def run_backtest_for_profile(self, algorithm, strategy, index_date): strategy.context = algorithm.context strategy.run_strategy(algorithm=algorithm, market_data=market_data) + def run_backtest_v2(self, strategy, algorithm): + config = self._configuration_service.get_config() + self._strategy_orchestrator_service.run_backtest_strategy( + algorithm=algorithm, strategy=strategy, config=config + ) + def generate_schedule( self, strategies, start_date, end_date ) -> pd.DataFrame: diff --git a/investing_algorithm_framework/services/configuration_service.py b/investing_algorithm_framework/services/configuration_service.py index c0a0ab66..9af0c9a2 100644 --- a/investing_algorithm_framework/services/configuration_service.py +++ b/investing_algorithm_framework/services/configuration_service.py @@ -15,7 +15,8 @@ "BACKTEST_DATA_DIRECTORY_NAME": "backtest_data", "SYMBOLS": None, "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", - "DATABASE_DIRECTORY_PATH": None + "DATABASE_DIRECTORY_PATH": None, + "DATABASE_DIRECTORY_NAME": "databases" } DEFAULT_FLASK_CONFIGURATION = { @@ -39,8 +40,12 @@ class ConfigurationService: def __init__(self): - self._config = DEFAULT_CONFIGURATION - self._flask_config = DEFAULT_FLASK_CONFIGURATION + self._config = DEFAULT_CONFIGURATION.copy() + self._flask_config = DEFAULT_FLASK_CONFIGURATION.copy() + + def initialize(self): + self._config = DEFAULT_CONFIGURATION.copy() + self._flask_config = DEFAULT_FLASK_CONFIGURATION.copy() @property def config(self): diff --git a/investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py b/investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py index 95f7f418..2c4802fa 100644 --- a/investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +++ b/investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py @@ -4,7 +4,9 @@ from investing_algorithm_framework.domain import MarketService, \ BacktestMarketDataSource, BACKTESTING_END_DATE, BACKTESTING_START_DATE, \ - BACKTESTING_INDEX_DATETIME, OperationalException + BACKTESTING_INDEX_DATETIME, OperationalException, OHLCVMarketDataSource, \ + TickerMarketDataSource, OrderBookMarketDataSource, MarketDataType, \ + TimeFrame from investing_algorithm_framework.services.configuration_service import \ ConfigurationService from investing_algorithm_framework.services.market_credential_service \ @@ -22,18 +24,23 @@ class BacktestMarketDataSourceService(MarketDataSourceService): The prepare_data method of BacktestMarketDataSource is called in the constructor. + + The main difference between MarketDataSourceService and + the BacktestMarketDataSourceService is that it will + prepare the data for backtesting in the constructor. """ def __init__( self, - market_data_sources: List[BacktestMarketDataSource], market_service: MarketService, market_credential_service: MarketCredentialService, configuration_service: ConfigurationService, + market_data_sources: List[BacktestMarketDataSource] = None ): super().__init__( market_service=market_service, market_data_sources=None, market_credential_service=market_credential_service, + configuration_service=configuration_service ) self.market_data_sources = [] @@ -42,62 +49,113 @@ def __init__( for market_data_source in market_data_sources: self.add(market_data_source) - self._configuration_service: ConfigurationService = \ - configuration_service + def initialize_market_data_sources(self): + config = self._configuration_service.get_config() + backtest_start_date = config[BACKTESTING_START_DATE] + backtest_end_date = config[BACKTESTING_END_DATE] + backtest_market_data_sources = [ + market_data_source.to_backtest_market_data_source() for + market_data_source in self._market_data_sources + ] + + # Filter out the None values + backtest_market_data_sources = [ + market_data_source for market_data_source in + backtest_market_data_sources if market_data_source is not None + ] for backtest_market_data_source in tqdm( - market_data_sources, + backtest_market_data_sources, total=len(self._market_data_sources), desc="Preparing backtest market data", colour="GREEN" ): + backtest_market_data_source.market_credential_service = \ + self._market_credential_service + backtest_market_data_source.prepare_data( + config=config, + backtest_start_date=backtest_start_date, + backtest_end_date=backtest_end_date + ) - if backtest_market_data_source is not None: - backtest_market_data_source.market_credentials_service = \ - self._market_credential_service - backtest_market_data_source.prepare_data( - config=configuration_service.get_config(), - backtest_start_date=configuration_service - .get_config()[BACKTESTING_START_DATE], - backtest_end_date=configuration_service - .get_config()[BACKTESTING_END_DATE], - ) + self.market_data_sources = backtest_market_data_sources def get_data(self, identifier): """ This method is used to get the data for backtesting. It loops over all the backtest market data sources and returns the data for the given identifier (If there is a match). + + If there is no match, it raises an OperationalException. + + Args: + identifier: The identifier of the market data source + + Returns: + The data for the given identifier """ - for backtest_market_data_source in self._market_data_sources: - if backtest_market_data_source.identifier == identifier: - backtest_market_data_source.market_credentials_service = \ - self._market_credential_service - return backtest_market_data_source.get_data( - backtest_index_date=self._configuration_service - .config[BACKTESTING_INDEX_DATETIME], + for market_data_source in self._market_data_sources: + + if market_data_source.get_identifier() == identifier: + config = self._configuration_service.get_config() + backtest_index_date = config[BACKTESTING_INDEX_DATETIME] + data = market_data_source.get_data( + date=backtest_index_date, config=config ) + result = { + "data": data, + "type": None, + "symbol": None, + "time_frame": None + } + + # Add metadata to the data + if isinstance(market_data_source, OHLCVMarketDataSource): + result["type"] = MarketDataType.OHLCV + time_frame = TimeFrame.from_value( + market_data_source.time_frame + ) + result["time_frame"] = time_frame.value + result["symbol"] = market_data_source.symbol + return result + + if isinstance(market_data_source, TickerMarketDataSource): + result["type"] = MarketDataType.TICKER + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + + if isinstance(market_data_source, OrderBookMarketDataSource): + result["type"] = MarketDataType.ORDER_BOOK + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + + result["type"] = MarketDataType.CUSTOM + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + raise OperationalException( f"Backtest market data source not found for {identifier}" ) - def get_ticker(self, symbol, market): - market_data_source = self.get_ticker_market_data_source( + def get_ticker(self, symbol, market=None): + ticker_market_data_source = self.get_ticker_market_data_source( symbol=symbol, market=market ) - if market_data_source is None: + if ticker_market_data_source is None: raise OperationalException( f"Backtest ticker data source " f"not found for {symbol} and market {market}" ) - market_data_source.market_credentials_service = \ - self._market_credential_service - return market_data_source.get_data( - backtest_index_date=self._configuration_service - .config[BACKTESTING_INDEX_DATETIME], + config = self._configuration_service.get_config() + backtest_index_date = config[BACKTESTING_INDEX_DATETIME] + return ticker_market_data_source.get_data( + date=backtest_index_date, config=config ) def get_order_book(self, symbol, market): @@ -106,9 +164,10 @@ def get_order_book(self, symbol, market): ) market_data_source.market_credential_service = \ self._market_credential_service + config = self._configuration_service.get_config() + backtest_index_date = config[BACKTESTING_INDEX_DATETIME] return market_data_source.get_data( - backtest_index_date=self._configuration_service - .config[BACKTESTING_INDEX_DATETIME], + date=backtest_index_date, config=config ) def get_ohlcv( @@ -124,11 +183,10 @@ def get_ohlcv( ) market_data_source.market_credential_service = \ self._market_credential_service + config = self._configuration_service.get_config() + backtest_index_date = config[BACKTESTING_INDEX_DATETIME] return market_data_source.get_data( - from_timestamp=from_timestamp, - to_timestamp=to_timestamp, - backtest_index_date=self._configuration_service - .config[BACKTESTING_INDEX_DATETIME], + date=backtest_index_date, config=config ) def is_ohlcv_data_source_present(self, symbol, time_frame, market): diff --git a/investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py b/investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py index 4f4a6196..78a971a0 100644 --- a/investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +++ b/investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py @@ -1,8 +1,12 @@ from typing import List +from datetime import datetime, timezone from investing_algorithm_framework.domain import MarketService, \ MarketDataSource, OHLCVMarketDataSource, TickerMarketDataSource, \ - OrderBookMarketDataSource, TimeFrame, OperationalException + OrderBookMarketDataSource, TimeFrame, OperationalException, \ + MarketDataType +from investing_algorithm_framework.services.configuration_service import \ + ConfigurationService from investing_algorithm_framework.services.market_credential_service \ import MarketCredentialService @@ -23,7 +27,8 @@ def __init__( self, market_service: MarketService, market_credential_service: MarketCredentialService, - market_data_sources: List[MarketDataSource] = None, + configuration_service: ConfigurationService, + market_data_sources: List[MarketDataSource] = None ): if market_data_sources is not None: @@ -33,26 +38,43 @@ def __init__( self._market_service: MarketService = market_service self._market_credential_service: MarketCredentialService = \ market_credential_service + self._configuration_service = configuration_service + + def initialize_market_data_sources(self): + + for market_data_source in self._market_data_sources: + market_data_source.market_credential_service = \ + self._market_credential_service def get_ticker(self, symbol, market=None): ticker_market_data_source = self.get_ticker_market_data_source( symbol=symbol, market=market ) + config = self._configuration_service.get_config() + date = config.get("DATE_TIME", None) if ticker_market_data_source is not None: + + if date is not None: + return ticker_market_data_source.get_data( + end_date=date, config=config + ) + return ticker_market_data_source.get_data( - market_credential_service=self._market_credential_service + end_date=datetime.now(tz=timezone.utc), config=config ) + return self._market_service.get_ticker(symbol, market) def get_order_book(self, symbol, market=None): order_book_market_data_source = self.get_order_book_market_data_source( symbol=symbol, market=market ) + config = self._configuration_service.get_config() if order_book_market_data_source is not None: return order_book_market_data_source.get_data( - market_credential_service=self._market_credential_service + end_date=datetime.now(tz=timezone.utc), config=config ) return self._market_service.get_order_book(symbol, market) @@ -68,12 +90,11 @@ def get_ohlcv( ohlcv_market_data_source = self.get_ohlcv_market_data_source( symbol=symbol, market=market, time_frame=time_frame ) + config = self._configuration_service.get_config() if ohlcv_market_data_source is not None: return ohlcv_market_data_source.get_data( - from_timestamp=from_timestamp, - to_timestamp=to_timestamp, - market_credential_service=self._market_credential_service + end_date=datetime.now(tz=timezone.utc), config=config ) return self._market_service.get_ohlcv( @@ -84,21 +105,130 @@ def get_ohlcv( to_timestamp=to_timestamp ) + def get_data_for_strategy(self, strategy): + """ + Function to get the data for the strategy. It loops over all + the market data sources in the strategy and returns the + data for each + + Args: + strategy: The strategy for which the data is required + + Returns: + The data for the strategy in dictionary format with + the keys being the identifier of the market data sources + """ + identifiers = [] + + if strategy.market_data_sources is not None: + for market_data_source in strategy.market_data_sources: + + if isinstance(market_data_source, MarketDataSource): + identifiers.append(market_data_source.get_identifier()) + elif isinstance(market_data_source, str): + identifiers.append(market_data_source) + else: + raise OperationalException( + "Market data source must be a string " + + "or MarketDataSource" + ) + + market_data = {"metadata": { + MarketDataType.OHLCV: {}, + MarketDataType.TICKER: {}, + MarketDataType.ORDER_BOOK: {}, + MarketDataType.CUSTOM: {} + }} + + for identifier in identifiers: + result_data = self.get_data(identifier) + + if "symbol" in result_data and result_data["symbol"] is not None \ + and "type" in result_data \ + and result_data["type"] is not None: + type = result_data["type"] + symbol = result_data["symbol"] + time_frame = result_data["time_frame"] + + if symbol not in market_data["metadata"][type]: + market_data["metadata"][type][symbol] = {} + + if time_frame is None: + market_data["metadata"][type][symbol] = identifier + + if time_frame is not None and \ + time_frame not in \ + market_data["metadata"][type][symbol]: + market_data["metadata"][type][symbol][time_frame] = identifier + + market_data[identifier] = result_data["data"] + return market_data + def get_data(self, identifier): for market_data_source in self._market_data_sources: + if market_data_source.get_identifier() == identifier: - return market_data_source.get_data( - market_credential_service=self._market_credential_service - ) + config = self._configuration_service.get_config() + + config = self._configuration_service.get_config() + date = config.get("DATE_TIME", None) + + if date is not None: + data = market_data_source.get_data( + end_date=date, config=config + ) + + if "DATE_TIME" in config: + data = market_data_source.get_data( + end_date=config["DATE_TIME"], config=config, + ) + else: + data = market_data_source.get_data( + end_date=datetime.now(tz=timezone.utc), config=config, + ) + + result = { + "data": data, + "type": None, + "symbol": None, + "time_frame": None + } + + # Add metadata to the data + if isinstance(market_data_source, OHLCVMarketDataSource): + result["type"] = MarketDataType.OHLCV - if isinstance(identifier, str): - raise OperationalException( - f"Market data source with identifier {identifier} not found. " - "Please make sure that the market data source is " - "registered to the app if you refer to it by " - "identifier in your strategy." - ) + time_frame = market_data_source.time_frame + + if time_frame is not None: + result["time_frame"] = time_frame.value + else: + result["time_frame"] = TimeFrame.CURRENT + + result["symbol"] = market_data_source.symbol + return result + + if isinstance(market_data_source, TickerMarketDataSource): + result["type"] = MarketDataType.TICKER + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + + if isinstance(market_data_source, OrderBookMarketDataSource): + result["type"] = MarketDataType.ORDER_BOOK + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + + result["type"] = MarketDataType.CUSTOM + result["time_frame"] = TimeFrame.CURRENT + result["symbol"] = market_data_source.symbol + return result + + raise OperationalException( + f"Backtest market data source not found for {identifier}" + ) def get_ticker_market_data_source(self, symbol, market=None): @@ -208,6 +338,8 @@ def add(self, market_data_source): market_data_source.get_identifier(): return + market_data_source.market_credential_service = \ + self._market_credential_service self._market_data_sources.append(market_data_source) def get_market_data_sources(self): diff --git a/investing_algorithm_framework/services/order_service/order_backtest_service.py b/investing_algorithm_framework/services/order_service/order_backtest_service.py index d836be3b..54e88e7e 100644 --- a/investing_algorithm_framework/services/order_service/order_backtest_service.py +++ b/investing_algorithm_framework/services/order_service/order_backtest_service.py @@ -3,8 +3,7 @@ import polars as pl from investing_algorithm_framework.domain import BACKTESTING_INDEX_DATETIME, \ - OrderStatus, BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, \ - OperationalException, OrderSide, Order, TimeFrame + OrderStatus, OrderSide, Order, MarketDataType from investing_algorithm_framework.services.market_data_source_service \ import BacktestMarketDataSourceService from .order_service import OrderService @@ -17,6 +16,7 @@ class OrderBacktestService(OrderService): def __init__( self, order_repository, + trade_service, position_repository, portfolio_repository, portfolio_configuration_service, @@ -25,6 +25,7 @@ def __init__( market_data_source_service: BacktestMarketDataSourceService, ): super(OrderService, self).__init__(order_repository) + self.trade_service = trade_service self.order_repository = order_repository self.position_repository = position_repository self.portfolio_repository = portfolio_repository @@ -56,67 +57,37 @@ def execute_order(self, order_id, portfolio): ) return order - def check_pending_orders(self): + def check_pending_orders(self, market_data): """ - Function to check if any pending orders have executed. + Function to check if any pending orders have executed. It querys the + open orders and checks if the order has executed based on the OHLCV + data. If the order has executed, the order status is set to CLOSED + and the filled amount is set to the order amount. + + Args: + market_data (dict): Dictionary containing the market data + + Returns: + None """ pending_orders = self.get_all({"status": OrderStatus.OPEN.value}) - logger.info(f"Checking {len(pending_orders)} open orders") - config = self.configuration_service.get_config() + meta_data = market_data["metadata"] for order in pending_orders: - symbol = f"{order.target_symbol.upper()}" \ - f"/{order.trading_symbol.upper()}" - position = self.position_repository.get(order.position_id) - portfolio = self.portfolio_repository.get(position.portfolio_id) - time_frame = None - - if BACKTESTING_PENDING_ORDER_CHECK_INTERVAL in \ - self.configuration_service.config: - time_frame = self.configuration_service\ - .config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL] - - # Get the lowest time frame available - if time_frame is None: - # Loop through all time frames - for tf in TimeFrame: - if self._market_data_source_service\ - .is_ohlcv_data_source_present( - symbol=symbol, - market=portfolio.market, - time_frame=tf - ): - time_frame = tf - break - - if not self._market_data_source_service\ - .is_ohlcv_data_source_present( - symbol=symbol, - market=portfolio.market, - time_frame=time_frame - ): - raise OperationalException( - f"OHLCV data source not found for {symbol} " - f"and market {portfolio.market} for order check " - f"time frame " - f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]}. " - f"Cannot check pending orders for symbol {symbol} " - f"with market {portfolio.market}. Please add a ohlcv data" - f"source for {symbol} and market {portfolio.market} with " - f"time frame " - f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]} " - ) + ohlcv_meta_data = meta_data[MarketDataType.OHLCV] + + if order.get_symbol() not in ohlcv_meta_data: + continue - config = self.configuration_service.get_config() - df = self._market_data_source_service.get_ohlcv( - symbol=symbol, - market=portfolio.market, - time_frame=time_frame, - to_timestamp=config[BACKTESTING_INDEX_DATETIME], - from_timestamp=order.get_created_at(), + timeframes = ohlcv_meta_data[order.get_symbol()].keys() + sorted_timeframes = sorted(timeframes) + most_granular_interval = sorted_timeframes[0] + identifier = ( + ohlcv_meta_data[order.get_symbol()][most_granular_interval] ) + data = market_data[identifier] - if self.has_executed(order, df): + if self.has_executed(order, data): self.update( order.id, { @@ -127,7 +98,6 @@ def check_pending_orders(self): .config[BACKTESTING_INDEX_DATETIME] } ) - break def cancel_order(self, order): self.check_pending_orders() diff --git a/investing_algorithm_framework/services/order_service/order_service.py b/investing_algorithm_framework/services/order_service/order_service.py index 7abf2bd2..edfc7400 100644 --- a/investing_algorithm_framework/services/order_service/order_service.py +++ b/investing_algorithm_framework/services/order_service/order_service.py @@ -1,6 +1,5 @@ import logging from datetime import datetime -from queue import PriorityQueue from dateutil.tz import tzutc @@ -23,7 +22,8 @@ def __init__( portfolio_repository, portfolio_configuration_service, portfolio_snapshot_service, - market_credential_service + market_credential_service, + trade_service ): super(OrderService, self).__init__(order_repository) self.configuration_service = configuration_service @@ -34,6 +34,7 @@ def __init__( self.portfolio_configuration_service = portfolio_configuration_service self.portfolio_snapshot_service = portfolio_snapshot_service self.market_credential_service = market_credential_service + self.trade_service = trade_service def create(self, data, execute=True, validate=True, sync=True) -> Order: portfolio_id = data["portfolio_id"] @@ -68,6 +69,10 @@ def create(self, data, execute=True, validate=True, sync=True) -> Order: .get(portfolio.identifier) self.execute_order(order_id, portfolio) + # Create trade from buy order if buy order + if OrderSide.BUY.equals(order.get_order_side()): + self.trade_service.create_trade_from_buy_order(order) + return order def update(self, object_id, data): @@ -208,7 +213,8 @@ def validate_sell_order(self, order_data, portfolio): if position.get_amount() < order_data["amount"]: raise OperationalException( - "Order amount is larger then amount of open position" + f"Order amount {order_data['amount']} is larger " + + f"then amount of open position {position.get_amount()}" ) if not order_data["trading_symbol"] == portfolio.trading_symbol: @@ -308,9 +314,20 @@ def validate_market_order(self, order_data, portfolio): "Sell order amount larger then position size" ) - def check_pending_orders(self): - pending_orders = self.get_all({"status": OrderStatus.OPEN.value}) - logger.info(f"Checking {len(pending_orders)} open orders") + def check_pending_orders(self, portfolio=None): + """ + Function to check if + """ + + if portfolio is not None: + pending_orders = self.get_all( + { + "status": OrderStatus.OPEN.value, + "portfolio_id": portfolio.id + } + ) + else: + pending_orders = self.get_all({"status": OrderStatus.OPEN.value}) for order in pending_orders: position = self.position_repository.get(order.position_id) @@ -354,6 +371,18 @@ def _sync_portfolio_with_created_buy_order(self, order): ) def _sync_portfolio_with_created_sell_order(self, order): + """ + Function to sync the portfolio with a created sell order. The + function will subtract the amount of the order from the position. + The portfolio will not be updated because the sell order has not been + filled. + + Args: + order: Order object representing the sell order + + Returns: + None + """ position = self.position_repository.get(order.position_id) self.position_repository.update( position.id, @@ -405,6 +434,10 @@ def _sync_with_buy_order_filled(self, previous_order, current_order): } ) + self.trade_service.update_trade_with_buy_order( + filled_difference, current_order + ) + def _sync_with_sell_order_filled(self, previous_order, current_order): filled_difference = current_order.get_filled() - \ previous_order.get_filled() @@ -441,8 +474,9 @@ def _sync_with_sell_order_filled(self, previous_order, current_order): + filled_size } ) - - self._close_trades(filled_difference, current_order) + self.trade_service.update_trade_with_sell_order_filled( + filled_difference, current_order + ) def _sync_with_buy_order_cancelled(self, order): remaining = order.get_amount() - order.get_filled() @@ -616,113 +650,6 @@ def _sync_with_sell_order_rejected(self, order): } ) - def _close_trades(self, amount_to_close, sell_order): - """ - Close trades for an executed sell order. - - This method will c - """ - matching_buy_orders = self.order_repository.get_all( - { - "position": sell_order.position_id, - "order_side": OrderSide.BUY.value, - "status": OrderStatus.CLOSED.value - } - ) - matching_buy_orders = [ - buy_order for buy_order in matching_buy_orders - if buy_order.get_trade_closed_at() is None - ] - order_queue = PriorityQueue() - - for order in matching_buy_orders: - order_queue.put(order) - - total_net_gain = 0 - total_cost = 0 - - while amount_to_close > 0 and not order_queue.empty(): - buy_order = order_queue.get() - closed_amount = buy_order.get_trade_closed_amount() - - # Check if the order has been closed - if closed_amount is None: - closed_amount = 0 - - available_to_close = buy_order.get_filled() - closed_amount - - if amount_to_close >= available_to_close: - to_be_closed = available_to_close - remaining = amount_to_close - to_be_closed - cost = buy_order.get_price() * to_be_closed - net_gain = (sell_order.get_price() - buy_order.get_price()) \ - * to_be_closed - amount_to_close = remaining - self.order_repository.update( - buy_order.id, - { - "trade_closed_amount": buy_order.get_filled(), - "trade_closed_at": sell_order.get_updated_at(), - "trade_closed_price": sell_order.get_price(), - "net_gain": buy_order.get_net_gain() + net_gain - } - ) - else: - to_be_closed = amount_to_close - net_gain = (sell_order.get_price() - buy_order.get_price()) \ - * to_be_closed - cost = buy_order.get_price() * amount_to_close - closed_amount = buy_order.get_trade_closed_amount() - - if closed_amount is None: - closed_amount = 0 - - self.order_repository.update( - buy_order.id, - { - "trade_closed_amount": closed_amount + to_be_closed, - "trade_closed_price": sell_order.get_price(), - "net_gain": buy_order.get_net_gain() + net_gain - } - ) - amount_to_close = 0 - - total_net_gain += net_gain - total_cost += cost - - position = self.position_repository.get(sell_order.position_id) - - # Update portfolio - portfolio = self.portfolio_repository.get(position.portfolio_id) - self.portfolio_repository.update( - portfolio.id, - { - "total_net_gain": portfolio.get_total_net_gain() - + total_net_gain, - "total_cost": portfolio.get_total_cost() - total_cost, - "net_size": portfolio.get_net_size() + total_net_gain - } - ) - # Update the position - position = self.position_repository.get(sell_order.position_id) - self.position_repository.update( - position.id, - { - "cost": position.get_cost() - total_cost - } - ) - - # Update the sell order - self.order_repository.update( - sell_order.id, - { - "trade_closed_amount": sell_order.get_filled(), - "trade_closed_at": sell_order.get_updated_at(), - "trade_closed_price": sell_order.get_price(), - "net_gain": sell_order.get_net_gain() + total_net_gain - } - ) - def create_snapshot(self, portfolio_id, created_at=None): if created_at is None: diff --git a/investing_algorithm_framework/services/portfolios/portfolio_sync_service.py b/investing_algorithm_framework/services/portfolios/portfolio_sync_service.py index 7090142b..2979f1af 100644 --- a/investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +++ b/investing_algorithm_framework/services/portfolios/portfolio_sync_service.py @@ -2,7 +2,7 @@ from investing_algorithm_framework.domain import OperationalException, \ AbstractPortfolioSyncService, RESERVED_BALANCES, SYMBOLS, \ - OrderSide, OrderStatus + ENVIRONMENT, Environment from investing_algorithm_framework.services.trade_service import TradeService logger = logging.getLogger(__name__) @@ -17,7 +17,7 @@ def __init__( self, trade_service: TradeService, configuration_service, - order_repository, + order_service, position_repository, portfolio_repository, portfolio_configuration_service, @@ -26,7 +26,7 @@ def __init__( ): self.trade_service = trade_service self.configuration_service = configuration_service - self.order_repository = order_repository + self.order_service = order_service self.position_repository = position_repository self.portfolio_repository = portfolio_repository self.market_credential_service = market_credential_service @@ -221,44 +221,13 @@ def sync_orders(self, portfolio): None """ - open_orders = self.order_repository.get_all( - { - "portfolio": portfolio.identifier, - "status": OrderStatus.OPEN.value - } - ) - - for order in open_orders: - external_orders = self.market_service.get_orders( - symbol=order.get_symbol(), - since=order.created_at, - market=portfolio.market - ) - - for external_order in external_orders: + config = self.configuration_service.get_config() - if order.external_id == external_order.external_id: - self.order_repository.update( - order.id, external_order.to_dict() - ) + if ENVIRONMENT in config \ + and Environment.BACKTEST.equals(config[ENVIRONMENT]): + return - def sync_trades(self, portfolio): - orders = self.order_repository.get_all( - { - "portfolio": portfolio.identifier, - "order_by_created_at_asc": True - } - ) - - sell_orders = [ - order for order in orders - if OrderSide.SELL.equals(order.order_side) - ] - - for sell_order in sell_orders: - self.trade_service.close_trades( - sell_order, sell_order.get_filled() - ) + self.order_service.check_pending_orders(portfolio=portfolio) def _get_symbols(self, portfolio): config = self.configuration_service.config diff --git a/investing_algorithm_framework/services/strategy_orchestrator_service.py b/investing_algorithm_framework/services/strategy_orchestrator_service.py index 44503f5b..9885b610 100644 --- a/investing_algorithm_framework/services/strategy_orchestrator_service.py +++ b/investing_algorithm_framework/services/strategy_orchestrator_service.py @@ -4,7 +4,7 @@ import schedule from investing_algorithm_framework.domain import StoppableThread, TimeUnit, \ - OperationalException, MarketDataSource + OperationalException from investing_algorithm_framework.services.market_data_source_service \ import MarketDataSourceService @@ -33,7 +33,11 @@ class StrategyOrchestratorService: The service that provides market data """ - def __init__(self, market_data_source_service: MarketDataSourceService): + def __init__( + self, + market_data_source_service: MarketDataSourceService, + configuration_service + ): self.history = {} self._strategies = [] self._tasks = [] @@ -43,6 +47,7 @@ def __init__(self, market_data_source_service: MarketDataSourceService): self.clear() self.market_data_source_service: MarketDataSourceService \ = market_data_source_service + self.configuration_service = configuration_service def cleanup_threads(self): @@ -63,25 +68,15 @@ def run_strategy(self, strategy, algorithm, sync=False): if matching_thread: return - market_data = {} - - if strategy.market_data_sources is not None \ - and len(strategy.market_data_sources) > 0: - - for data_id in strategy.market_data_sources: - if isinstance(data_id, MarketDataSource): - market_data[data_id.get_identifier()] = \ - data_id.get_data() - else: - market_data[data_id] = \ - self.market_data_source_service \ - .get_data(identifier=data_id) + market_data = \ + self.market_data_source_service.get_data_for_strategy(strategy) logger.info(f"Running strategy {strategy.worker_id}") if sync: strategy.run_strategy( - market_data=market_data, algorithm=algorithm + market_data=market_data, + algorithm=algorithm, ) else: self.iterations += 1 @@ -89,7 +84,7 @@ def run_strategy(self, strategy, algorithm, sync=False): target=strategy.run_strategy, kwargs={ "market_data": market_data, - "algorithm": algorithm + "algorithm": algorithm, } ) thread.name = strategy.worker_id @@ -98,6 +93,15 @@ def run_strategy(self, strategy, algorithm, sync=False): self.history[strategy.worker_id] = {"last_run": datetime.utcnow()} + def run_backtest_strategy(self, strategy, algorithm, config): + data = \ + self.market_data_source_service.get_data_for_strategy(strategy) + + strategy.run_strategy( + market_data=data, + algorithm=algorithm, + ) + def run_task(self, task, algorithm, sync=False): self.cleanup_threads() @@ -128,8 +132,17 @@ def run_task(self, task, algorithm, sync=False): def start(self, algorithm, number_of_iterations=None): """ - F + Function to start and schedule the strategies and tasks + + Args: + algorithm (Algorithm): The algorithm that will be used to run the + strategies and tasks + number_of_iterations (int): The number of iterations that the + strategies and tasks will run. If None, the + strategies and tasks + Returns: + None """ self.max_iterations = number_of_iterations diff --git a/investing_algorithm_framework/services/trade_service/trade_service.py b/investing_algorithm_framework/services/trade_service/trade_service.py index 66c45f95..7fc90446 100644 --- a/investing_algorithm_framework/services/trade_service/trade_service.py +++ b/investing_algorithm_framework/services/trade_service/trade_service.py @@ -1,401 +1,347 @@ import logging from queue import PriorityQueue -from typing import List +from dateutil import parser -from investing_algorithm_framework.domain import OrderStatus, OrderSide, \ - Trade, PeekableQueue, OrderType, TradeStatus, \ - OperationalException, Order, RoundingService -from investing_algorithm_framework.services.market_data_source_service import \ - MarketDataSourceService -from investing_algorithm_framework.services.position_service import \ - PositionService +from investing_algorithm_framework.domain import OrderStatus, \ + TradeStatus, Trade, OperationalException, MarketDataType +from investing_algorithm_framework.services.repository_service import \ + RepositoryService logger = logging.getLogger(__name__) -class TradeService: - """ - Service for managing trades - """ +class TradeService(RepositoryService): def __init__( self, + trade_repository, + position_repository, portfolio_repository, - order_service, - position_service, - market_data_source_service + market_data_source_service, + configuration_service ): + super(TradeService, self).__init__(trade_repository) self.portfolio_repository = portfolio_repository - self.order_service = order_service - self.market_data_source_service: MarketDataSourceService = \ - market_data_source_service - self.position_service: PositionService = position_service + self.market_data_source_service = market_data_source_service + self.position_repository = position_repository + self.configuration_service = configuration_service - def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]: + def create_trade_from_buy_order(self, buy_order) -> Trade: """ - Get open trades method + Function to create a trade from a buy order. If the given buy + order has its status set to CANCELED, EXPIRED, or REJECTED, + the trade object will not be created. If the given buy + order has its status set to CLOSED or OPEN, the trade object + will be created. The amount will be set to the filled amount. - :param target_symbol: str representing the target symbol - :param market: str representing the market - :return: list of Trade objects + Args: + buy_order: Order object representing the buy order + + Returns: + Trade object + """ + status = buy_order.get_status() + + if status in \ + [ + OrderStatus.CANCELED.value, + OrderStatus.EXPIRED.value, + OrderStatus.REJECTED.value + ]: + return None + + data = { + "buy_order": buy_order, + "target_symbol": buy_order.target_symbol, + "trading_symbol": buy_order.trading_symbol, + "amount": buy_order.filled, + "remaining": buy_order.filled, + "opened_at": buy_order.created_at, + "cost": buy_order.filled * buy_order.price + } + + if buy_order.filled > 0: + data["status"] = TradeStatus.OPEN.value + data["cost"] = buy_order.filled * buy_order.price + + return self.create(data) + + def update_trade_with_buy_order( + self, filled_difference, buy_order + ) -> Trade: + """ + Function to update a trade from a buy order. This function + checks if a trade exists for the buy order. If the given buy + order has its status set to CANCLED, EXPIRED, or REJECTED, the + trade will object will be removed. If the given buy order has + its status set to CLOSED or OPEN, the amount and + remaining of the trade object will be updated. + + Args: + filled_difference: float representing the difference between the + filled amount of the buy order and the filled amount + of the trade + buy_order: Order object representing the buy order + + Returns: + Trade object """ - portfolios = self.portfolio_repository.get_all() - trades = [] - - for portfolio in portfolios: - - if target_symbol is not None: - buy_orders = self.order_service.get_all({ - "status": OrderStatus.CLOSED.value, - "order_side": OrderSide.BUY.value, - "portfolio_id": portfolio.id, - "target_symbol": target_symbol - }) - sell_orders = self.order_service.get_all({ - "status": OrderStatus.OPEN.value, - "order_side": OrderSide.SELL.value, - "portfolio_id": portfolio.id, - "target_symbol": target_symbol - }) - else: - buy_orders = self.order_service.get_all({ - "status": OrderStatus.CLOSED.value, - "order_side": OrderSide.BUY.value, - "portfolio_id": portfolio.id - }) - sell_orders = self.order_service.get_all({ - "status": OrderStatus.OPEN.value, - "order_side": OrderSide.SELL.value, - "portfolio_id": portfolio.id - }) - - buy_orders = [ - buy_order for buy_order in buy_orders - if buy_order.get_trade_closed_at() is None - ] - sell_amount = sum([order.amount for order in sell_orders]) - - # Subtract the amount of the open sell orders - # from the amount of the buy orders - buy_orders_queue = PeekableQueue() - - for buy_order in buy_orders: - buy_orders_queue.enqueue(buy_order) - - while sell_amount > 0 and not buy_orders_queue.is_empty(): - first_buy_order = buy_orders_queue.peek() - available = first_buy_order.get_filled() \ - - first_buy_order.get_trade_closed_amount() - - if available > sell_amount: - remaining = available - sell_amount - sell_amount = 0 - first_buy_order.set_filled(remaining) - else: - sell_amount = sell_amount - available - buy_orders_queue.dequeue() - - for buy_order in buy_orders_queue: - symbol = buy_order.get_symbol() - current_price = buy_order.get_price() - try: - ticker = self.market_data_source_service.get_ticker( - symbol=symbol, market=market - ) - current_price = ticker["bid"] - except Exception as e: - logger.error(e) - - amount = buy_order.get_filled() - closed_amount = buy_order.get_trade_closed_amount() - - if closed_amount is not None: - amount = amount - closed_amount - - trades.append( - Trade( - buy_order_id=buy_order.id, - target_symbol=buy_order.get_target_symbol(), - trading_symbol=buy_order.get_trading_symbol(), - amount=amount, - open_price=buy_order.get_price(), - opened_at=buy_order.get_created_at(), - current_price=current_price - ) - ) + trade = self.find({"buy_order": buy_order.id}) - return trades + if trade is None: + raise OperationalException( + "Trade does not exist for buy order." + ) - def get_trades(self, market=None) -> List[Trade]: - """ - Get trades method + status = buy_order.get_status() - :param market: str representing the market - :param portfolio_id: str representing the portfolio id - :return: list of Trade objects - """ + if status in \ + [ + OrderStatus.CANCELED.value, + OrderStatus.EXPIRED.value, + OrderStatus.REJECTED.value + ]: + return self.delete(trade.id) - portfolios = self.portfolio_repository.get_all() - trades = [] - - for portfolio in portfolios: - buy_orders = self.order_service.get_all({ - "status": OrderStatus.CLOSED.value, - "order_side": OrderSide.BUY.value, - "portfolio_id": portfolio.id - }) - - for buy_order in buy_orders: - symbol = buy_order.get_symbol() - current_price = buy_order.get_price() - try: - ticker = self.market_data_source_service.get_ticker( - symbol=symbol, market=market - ) - current_price = ticker["bid"] - except Exception as e: - logger.error(e) - - trades.append( - Trade( - buy_order_id=buy_order.id, - target_symbol=buy_order.get_target_symbol(), - trading_symbol=buy_order.get_trading_symbol(), - amount=buy_order.get_amount(), - open_price=buy_order.get_price(), - closed_price=buy_order.get_trade_closed_price(), - closed_at=buy_order.get_trade_closed_at(), - opened_at=buy_order.get_created_at(), - current_price=current_price - ) - ) + trade = self.find({"order_id": buy_order.id}) + updated_data = { + "amount": trade.amount + filled_difference, + "remaining": trade.remaining + filled_difference, + "cost": trade.cost + filled_difference * buy_order.price + } - return trades + if filled_difference > 0: + updated_data["status"] = TradeStatus.OPEN.value - def get_closed_trades(self, portfolio_id=None) -> List[Trade]: - """ - Get closed trades method + trade = self.update(trade.id, updated_data) + return trade - :param portfolio_id: str representing the portfolio id - :return: list of Trade objects + def update_trade_with_sell_order_filled( + self, filled_amount, sell_order + ) -> Trade: """ - buy_orders = self.order_service.get_all({ - "status": OrderStatus.CLOSED.value, - "order_side": OrderSide.BUY.value, - "portfolio_id": portfolio_id - }) - return [ - Trade( - buy_order_id=order.id, - target_symbol=order.get_target_symbol(), - trading_symbol=order.get_trading_symbol(), - amount=order.get_amount(), - open_price=order.get_price(), - closed_price=order.get_trade_closed_price(), - closed_at=order.get_trade_closed_at(), - opened_at=order.get_created_at() - ) for order in buy_orders - if order.get_trade_closed_at() is not None - ] - - def close_trade(self, trade, market=None, precision=None) -> None: + Function to update a trade from a sell order that has been filled. + This function checks if a trade exists for the buy order. + If the given buy order has its status set to + CANCLED, EXPIRED, or REJECTED, the + trade will object will be removed. If the given buy order + has its status set to CLOSED or OPEN, the amount and + remaining of the trade object will be updated. + + Args: + sell_order: Order object representing the sell order that has + been filled. + filled_amount: float representing the filled amount of the sell + order + + Returns: + Trade object """ - Close trade method - param trade: Trade object - param market: str representing the market - raises OperationalException: if trade is already closed - or if the buy order belonging to the trade has no amount - - return: None - """ + # Only update the trade if the sell order has been filled + if sell_order.get_status() != OrderStatus.CLOSED.value: + return None - if trade.closed_at is not None: - raise OperationalException("Trade already closed.") + position = self.position_repository.find({ + "order_id": sell_order.id + }) + portfolio_id = position.portfolio_id + matching_trades = self.get_all({ + "status": TradeStatus.OPEN.value, + "target_symbol": sell_order.target_symbol, + "portfolio_id": portfolio_id + }) + target_symbol = sell_order.target_symbol + price = sell_order.price + updated_at = sell_order.updated_at + amount_to_close = filled_amount + order_queue = PriorityQueue() + total_net_gain = 0 + total_cost = 0 + total_remaining = 0 - order = self.order_service.get(trade.buy_order_id) + for trade in matching_trades: + if trade.remaining > 0: + total_remaining += trade.remaining + order_queue.put(trade) - if order.get_filled() <= 0: + if total_remaining < amount_to_close: raise OperationalException( - "Buy order belonging to the trade has no amount." + "Not enough amount to close in trades." ) - portfolio = self.portfolio_repository\ - .find({"position": order.position_id}) - position = self.position_service.find( - {"portfolio": portfolio.id, "symbol": order.get_target_symbol()} - ) - amount = order.get_amount() + while amount_to_close > 0 and not order_queue.empty(): + trade = order_queue.get() + available_to_close = trade.remaining - if precision is not None: - amount = RoundingService.round_down(amount, precision) + if amount_to_close >= available_to_close: + cost = trade.buy_order.price * available_to_close + net_gain = (price * available_to_close) - cost + amount_to_close = amount_to_close - available_to_close + self.update( + trade.id, { + "remaining": 0, + "closed_at": updated_at, + "net_gain": trade.net_gain + net_gain, + "status": TradeStatus.CLOSED.value + } + ) + self.repository.add_order_to_trade(trade, sell_order) + else: + to_be_closed = amount_to_close + cost = trade.buy_order.price * to_be_closed + net_gain = (price * to_be_closed) - cost + self.update( + trade.id, { + "remaining": trade.remaining - to_be_closed, + "net_gain": trade.net_gain + net_gain, + "orders": trade.orders.append(sell_order) + } + ) + self.repository.add_order_to_trade(trade, sell_order) + amount_to_close = 0 - if position.get_amount() < amount: - logger.warning( - f"Order amount {amount} is larger then amount " - f"of available {position.symbol} " - f"position: {position.get_amount()}, " - f"changing order amount to size of position" - ) - amount = position.get_amount() + total_net_gain += net_gain + total_cost += cost - symbol = order.get_symbol() - ticker = self.market_data_source_service.get_ticker( - symbol=symbol, market=market + portfolio = self.portfolio_repository.get(portfolio_id) + self.portfolio_repository.update( + portfolio.id, + { + "total_net_gain": portfolio.total_net_gain + total_net_gain, + "cost": portfolio.total_cost - total_cost + } ) - self.order_service.create( + position = self.position_repository.find( { - "portfolio_id": portfolio.id, - "trading_symbol": order.get_trading_symbol(), - "target_symbol": order.get_target_symbol(), - "amount": amount, - "order_side": OrderSide.SELL.value, - "order_type": OrderType.LIMIT.value, - "price": ticker["bid"], + "portfolio": portfolio.id, + "symbol": target_symbol + } + ) + self.position_repository.update( + position.id, + { + "cost": position.cost - total_cost, } ) - def count(self, query_params=None) -> int: - """ - Count method + def update_trades_with_market_data(self, market_data): + open_trades = self.get_all({"status": TradeStatus.OPEN.value}) + meta_data = market_data["metadata"] - :param query_params: dict representing the query parameters - :return: int representing the count - """ + for open_trade in open_trades: + ohlcv_meta_data = meta_data[MarketDataType.OHLCV] - portfolios = self.portfolio_repository.get_all() - trades = [] - - for portfolio in portfolios: - buy_orders = self.order_service.get_all({ - "status": OrderStatus.CLOSED.value, - "order_side": OrderSide.BUY.value, - "portfolio_id": portfolio.id - }) - - for buy_order in buy_orders: - trades.append( - Trade( - buy_order_id=buy_order.id, - target_symbol=buy_order.get_target_symbol(), - trading_symbol=buy_order.get_trading_symbol(), - amount=buy_order.get_amount(), - open_price=buy_order.get_price(), - closed_price=buy_order.get_trade_closed_price(), - closed_at=buy_order.get_trade_closed_at(), - opened_at=buy_order.get_created_at(), - ) + if open_trade.symbol not in ohlcv_meta_data: + continue + + timeframes = ohlcv_meta_data[open_trade.symbol].keys() + sorted_timeframes = sorted(timeframes) + most_granular_interval = sorted_timeframes[0] + identifier = ( + ohlcv_meta_data[open_trade.symbol][most_granular_interval] + ) + data = market_data[identifier] + + # Get last row of data + last_row = data.tail(1) + update_data = { + "last_reported_price": last_row["Close"][0], + "updated_at": parser.parse( + last_row["Datetime"][0] ) + } + price = last_row["Close"][0] + + if open_trade.trailing_stop_loss_percentage is not None: - if query_params is not None: - if "status" in query_params: + if open_trade.high_water_mark is None or \ + open_trade.high_water_mark < price: + update_data["high_water_mark"] = price - trade_status = TradeStatus\ - .from_value(query_params["status"]) + self.update(open_trade.id, update_data) - if trade_status == TradeStatus.OPEN: - trades = [ - trade for trade in trades - if trade.closed_at is None - ] - else: - trades = [ - trade for trade in trades - if trade.closed_at is not None - ] + def add_stop_loss(self, trade, percentage): + """ + Function to add a stop loss to a trade. The stop loss is + represented as a percentage of the open price. - return len(trades) + Args: + trade: Trade object representing the trade + percentage: float representing the percentage of the open price + that the stop loss should be set at - def close_trades(self, sell_order: Order, amount_to_close: float) -> None: + Returns: + None """ - Close trades method + trade = self.get(trade.id) + updated_data = { + "stop_loss_percentage": percentage + } + self.update(trade.id, updated_data) - :param sell_order: Order object representing the sell order - :param amount_to_close: float representing the amount to close - :return: None + def add_trailing_stop_loss(self, trade, percentage): """ - logger.info( - f"Closing trades for sell order {sell_order.get_id()} " - f"amount to close: {amount_to_close}" - ) + Function to add a trailing stop loss to a trade. The trailing stop loss + is represented as a percentage of the open price. - matching_buy_orders = self.order_service.get_all( - { - "position": sell_order.position_id, - "order_side": OrderSide.BUY.value, - "status": OrderStatus.CLOSED.value, - "order_by_created_at_asc": True - } - ) - matching_buy_orders = [ - buy_order for buy_order in matching_buy_orders - if buy_order.get_trade_closed_at() is None - ] - order_queue = PriorityQueue() + Args: + trade: Trade object representing the trade + percentage: float representing the percentage of the open price + that the trailing stop loss should be set at - for order in matching_buy_orders: - order_queue.put(order) + Returns: + None + """ + trade = self.get(trade.id) + updated_data = { + "trailing_stop_loss_percentage": percentage + } + self.update(trade.id, updated_data) - total_net_gain = 0 - total_cost = 0 + def get_triggered_stop_losses(self): + """ + Function to check if any trades have hit their stop loss. If a trade + has hit its stop loss, the trade is added to a list of + triggered trades. This list is then returned. - while amount_to_close > 0 and not order_queue.empty(): - buy_order = order_queue.get() - closed_amount = buy_order.get_trade_closed_amount() + Returns: + List of Trade objects + """ + triggered_trades = [] + query = { + "status": TradeStatus.OPEN.value, + "stop_loss_percentage_not_none": True + } + open_trades = self.get_all(query) - # Check if the order has been closed - if closed_amount is None: - closed_amount = 0 + for open_trade in open_trades: - available_to_close = buy_order.get_filled() - closed_amount + if open_trade.is_stop_loss_triggered(): + triggered_trades.append(open_trade) - if amount_to_close >= available_to_close: - to_be_closed = available_to_close - remaining = amount_to_close - to_be_closed - cost = buy_order.get_price() * to_be_closed - net_gain = (sell_order.get_price() - buy_order.get_price()) \ - * to_be_closed - amount_to_close = remaining - self.order_service.repository.update( - buy_order.id, - { - "trade_closed_amount": buy_order.get_filled(), - "trade_closed_at": sell_order.get_updated_at(), - "trade_closed_price": sell_order.get_price(), - "net_gain": buy_order.get_net_gain() + net_gain - } - ) - else: - to_be_closed = amount_to_close - net_gain = (sell_order.get_price() - buy_order.get_price()) \ - * to_be_closed - cost = buy_order.get_price() * amount_to_close - closed_amount = buy_order.get_trade_closed_amount() - - if closed_amount is None: - closed_amount = 0 - - self.order_service.repository.update( - buy_order.id, - { - "trade_closed_amount": closed_amount + to_be_closed, - "trade_closed_price": sell_order.get_price(), - "net_gain": buy_order.get_net_gain() + net_gain - } - ) - amount_to_close = 0 + return triggered_trades - total_net_gain += net_gain - total_cost += cost + def get_triggered_trailing_stop_losses(self): + """ + Function to check if any trades have hit their stop loss. If a trade + has hit its stop loss, the trade is added to a list of + triggered trades. This list is then returned. - # Update the sell order - self.order_service.repository.update( - sell_order.get_id(), - { - "trade_closed_amount": sell_order.get_filled(), - "trade_closed_at": sell_order.get_updated_at(), - "trade_closed_price": sell_order.get_price(), - "net_gain": sell_order.get_net_gain() + total_net_gain - } - ) + Returns: + List of Trade objects + """ + triggered_trades = [] + query = { + "status": TradeStatus.OPEN.value, + "trailing_stop_loss_percentage_not_none": True + } + open_trades = self.get_all(query) + + for open_trade in open_trades: + + if open_trade.is_trailing_stop_loss_triggered(): + triggered_trades.append(open_trade) + + return triggered_trades diff --git a/pyproject.toml b/pyproject.toml index 0b516130..226e21f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ exclude = ["tests", "static", "examples", "docs"] [tool.poetry.dependencies] -python = ">=3.10" +python = ">=3.9" wrapt = "^1.16.0" Flask = "^2.3.2" Flask-Migrate = "^2.6.0" diff --git a/tests/app/algorithm/test_check_order_status.py b/tests/app/algorithm/test_check_order_status.py index c18402ea..9cf743c2 100644 --- a/tests/app/algorithm/test_check_order_status.py +++ b/tests/app/algorithm/test_check_order_status.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ OrderStatus, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -22,6 +22,7 @@ class Test(TestBase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def test_check_order_status(self): order_repository = self.app.container.order_repository() diff --git a/tests/app/algorithm/test_close_position.py b/tests/app/algorithm/test_close_position.py index 91199efb..438c9db3 100644 --- a/tests/app/algorithm/test_close_position.py +++ b/tests/app/algorithm/test_close_position.py @@ -3,7 +3,8 @@ from investing_algorithm_framework import PortfolioConfiguration, \ CSVTickerMarketDataSource, MarketCredential -from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub +from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub, \ + MarketDataSourceServiceStub class Test(TestBase): @@ -28,6 +29,7 @@ class Test(TestBase): None, None ) + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super(Test, self).setUp() @@ -38,7 +40,7 @@ def setUp(self) -> None: csv_file_path=os.path.join( self.resource_directory, "market_data_sources", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" ) )) diff --git a/tests/app/algorithm/test_close_trade.py b/tests/app/algorithm/test_close_trade.py index ec2b25b2..ca1b829d 100644 --- a/tests/app/algorithm/test_close_trade.py +++ b/tests/app/algorithm/test_close_trade.py @@ -3,7 +3,8 @@ from investing_algorithm_framework import PortfolioConfiguration, \ CSVTickerMarketDataSource, MarketCredential, OperationalException -from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub +from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub, \ + MarketDataSourceServiceStub class Test(TestBase): @@ -28,6 +29,7 @@ class Test(TestBase): None, None ) + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super().setUp() @@ -38,7 +40,7 @@ def setUp(self) -> None: csv_file_path=os.path.join( self.resource_directory, "market_data_sources", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" ) )) @@ -54,14 +56,14 @@ def test_close_trade(self): btc_position = self.app.algorithm.get_position("BTC") self.assertIsNotNone(btc_position) self.assertEqual(0, btc_position.get_amount()) - self.assertEqual(0, len(self.app.algorithm.get_trades())) + self.assertEqual(1, len(self.app.algorithm.get_trades())) order_service = self.app.container.order_service() order_service.check_pending_orders() self.assertEqual(1, len(self.app.algorithm.get_trades())) trades = self.app.algorithm.get_trades() trade = trades[0] - self.assertIsNotNone(trade.get_amount()) - self.assertEqual(Decimal(1), trade.get_amount()) + self.assertIsNotNone(trade.amount) + self.assertEqual(Decimal(1), trade.amount) self.app.algorithm.close_trade(trade) self.assertEqual(1, len(self.app.algorithm.get_trades())) order_service.check_pending_orders() @@ -80,14 +82,14 @@ def test_close_trade_with_already_closed_trade(self): btc_position = self.app.algorithm.get_position("BTC") self.assertIsNotNone(btc_position) self.assertEqual(0, btc_position.get_amount()) - self.assertEqual(0, len(self.app.algorithm.get_trades())) + self.assertEqual(1, len(self.app.algorithm.get_trades())) order_service = self.app.container.order_service() order_service.check_pending_orders() self.assertEqual(1, len(self.app.algorithm.get_trades())) trades = self.app.algorithm.get_trades() trade = trades[0] - self.assertIsNotNone(trade.get_amount()) - self.assertEqual(Decimal(1), trade.get_amount()) + self.assertIsNotNone(trade.amount) + self.assertEqual(Decimal(1), trade.amount) self.app.algorithm.close_trade(trade) self.assertEqual(1, len(self.app.algorithm.get_trades())) order_service.check_pending_orders() diff --git a/tests/app/algorithm/test_create_limit_buy_order.py b/tests/app/algorithm/test_create_limit_buy_order.py index dcf73464..42472bd8 100644 --- a/tests/app/algorithm/test_create_limit_buy_order.py +++ b/tests/app/algorithm/test_create_limit_buy_order.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ OrderStatus, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -20,6 +20,7 @@ class Test(TestBase): external_balances = { "EUR": 1000 } + market_data_source_service = MarketDataSourceServiceStub() def count_decimals(self, number): decimal_str = str(number) diff --git a/tests/app/algorithm/test_create_limit_sell_order.py b/tests/app/algorithm/test_create_limit_sell_order.py index f23d17b6..414e2dfd 100644 --- a/tests/app/algorithm/test_create_limit_sell_order.py +++ b/tests/app/algorithm/test_create_limit_sell_order.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ OrderStatus, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -20,6 +20,7 @@ class Test(TestBase): external_balances = { "EUR": 1000 } + market_data_source_service = MarketDataSourceServiceStub() def test_create_limit_sell_order(self): self.app.run(number_of_iterations=1) diff --git a/tests/app/algorithm/test_create_market_sell_order.py b/tests/app/algorithm/test_create_market_sell_order.py index d20f17a4..52be6a98 100644 --- a/tests/app/algorithm/test_create_market_sell_order.py +++ b/tests/app/algorithm/test_create_market_sell_order.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ OrderType, OrderSide, OrderStatus, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -21,6 +21,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_create_market_sell_order(self): portfolio = self.app.algorithm.get_portfolio() diff --git a/tests/app/algorithm/test_get_allocated.py b/tests/app/algorithm/test_get_allocated.py index f230c5a5..0cb3c01f 100644 --- a/tests/app/algorithm/test_get_allocated.py +++ b/tests/app/algorithm/test_get_allocated.py @@ -1,6 +1,7 @@ from investing_algorithm_framework import TradingStrategy, TimeUnit, \ MarketCredential, PortfolioConfiguration -from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub +from tests.resources import TestBase, RandomPriceMarketDataSourceServiceStub, \ + MarketDataSourceServiceStub class StrategyOne(TradingStrategy): @@ -39,6 +40,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_get_allocated(self): self.app.container.market_data_source_service.override( diff --git a/tests/app/algorithm/test_get_closed_trades.py b/tests/app/algorithm/test_get_closed_trades.py index f28f8769..3a0fa512 100644 --- a/tests/app/algorithm/test_get_closed_trades.py +++ b/tests/app/algorithm/test_get_closed_trades.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ CSVTickerMarketDataSource, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super(Test, self).setUp() @@ -33,18 +34,17 @@ def setUp(self) -> None: csv_file_path=os.path.join( self.resource_directory, "market_data_sources", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" ) )) def test_get_open_trades(self): - self.app.algorithm.create_limit_order( + order = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=20 ) - order = self.app.algorithm.get_order() self.assertIsNotNone(order) self.assertEqual(0, len(self.app.algorithm.get_closed_trades())) order_service = self.app.container.order_service() @@ -55,7 +55,6 @@ def test_get_open_trades(self): self.assertEqual(20, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNone(trade.closed_price) self.assertIsNone(trade.closed_at) self.app.algorithm.create_limit_order( target_symbol="BTC", diff --git a/tests/app/algorithm/test_get_number_of_positions.py b/tests/app/algorithm/test_get_number_of_positions.py index 11107ac9..b7c1d771 100644 --- a/tests/app/algorithm/test_get_number_of_positions.py +++ b/tests/app/algorithm/test_get_number_of_positions.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_get_number_of_positions(self): trading_symbol_position = self.app.algorithm.get_position("EUR") diff --git a/tests/app/algorithm/test_get_open_trades.py b/tests/app/algorithm/test_get_open_trades.py index d8a386f6..af4fb660 100644 --- a/tests/app/algorithm/test_get_open_trades.py +++ b/tests/app/algorithm/test_get_open_trades.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ CSVTickerMarketDataSource, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,28 +23,30 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super(Test, self).setUp() - self.app.add_market_data_source(CSVTickerMarketDataSource( - identifier="BTC/EUR-ticker", - market="BITVAVO", - symbol="BTC/EUR", - csv_file_path=os.path.join( - self.resource_directory, - "market_data_sources_for_testing", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + self.app.add_market_data_source( + CSVTickerMarketDataSource( + identifier="BTC/EUR-ticker", + market="BITVAVO", + symbol="BTC/EUR", + csv_file_path=os.path.join( + self.resource_directory, + "market_data_sources_for_testing", + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" + ) ) - )) + ) def test_get_open_trades(self): - self.app.algorithm.create_limit_order( + order = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=20 ) - order = self.app.algorithm.get_order() self.assertIsNotNone(order) self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) order_service = self.app.container.order_service() @@ -55,7 +57,6 @@ def test_get_open_trades(self): self.assertEqual(20, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNone(trade.closed_price) self.assertIsNone(trade.closed_at) self.app.algorithm.create_limit_order( target_symbol="BTC", @@ -63,7 +64,7 @@ def test_get_open_trades(self): order_side="SELL", amount=20 ) - self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) + self.assertEqual(1, len(self.app.algorithm.get_open_trades("BTC"))) order_service.check_pending_orders() self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) @@ -74,13 +75,12 @@ def test_get_open_trades_with_close_trades(self): order_side="BUY", amount=5 ) - self.app.algorithm.create_limit_order( + order = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=5 ) - order = self.app.algorithm.get_order() self.assertIsNotNone(order) self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) order_service = self.app.container.order_service() @@ -91,7 +91,6 @@ def test_get_open_trades_with_close_trades(self): self.assertEqual(5, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNone(trade.closed_price) self.assertIsNone(trade.closed_at) self.assertEqual( 0, @@ -110,7 +109,7 @@ def test_get_open_trades_with_close_trades(self): self.app.algorithm.get_orders(order_side="SELL", status="OPEN") ) ) - self.assertEqual(1, len(self.app.algorithm.get_open_trades("BTC"))) + self.assertEqual(2, len(self.app.algorithm.get_open_trades("BTC"))) self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, @@ -120,23 +119,25 @@ def test_get_open_trades_with_close_trades(self): self.assertEqual(2, len( self.app.algorithm.get_orders(order_side="SELL", status="OPEN")) ) - self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) + self.assertEqual(2, len(self.app.algorithm.get_open_trades("BTC"))) def test_get_open_trades_with_close_trades_of_partial_buy_orders(self): - self.app.algorithm.create_limit_order( + order_one = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=5 ) - self.app.algorithm.create_limit_order( + order_two = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=5 ) - order = self.app.algorithm.get_order() - self.assertIsNotNone(order) + order_one_id = order_one.id + order_two_id = order_two.id + self.assertIsNotNone(order_one) + self.assertIsNotNone(order_two) self.assertEqual(0, len(self.app.algorithm.get_open_trades("BTC"))) order_service = self.app.container.order_service() order_service.check_pending_orders() @@ -146,7 +147,6 @@ def test_get_open_trades_with_close_trades_of_partial_buy_orders(self): self.assertEqual(5, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNone(trade.closed_price) self.assertIsNone(trade.closed_at) self.assertEqual( 0, @@ -165,13 +165,15 @@ def test_get_open_trades_with_close_trades_of_partial_buy_orders(self): self.app.algorithm.get_orders(order_side="SELL", status="OPEN") ) ) - open_trades = self.app.algorithm.get_open_trades("BTC") - self.assertEqual(2.5, open_trades[0].amount) - self.assertEqual(5, open_trades[1].amount) - self.app.algorithm.check_pending_orders() - open_trades = self.app.algorithm.get_open_trades("BTC") - self.assertEqual(2.5, open_trades[0].amount) - self.assertEqual(5, open_trades[1].amount) + trade_one = self.app.algorithm.get_trade(order_id=order_one_id) + trade_two = self.app.algorithm.get_trade(order_id=order_two_id) + self.assertEqual(5, trade_one.remaining) + self.assertEqual(5, trade_two.remaining) + self.app.algorithm.order_service.check_pending_orders() + trade_one = self.app.algorithm.get_trade(order_id=order_one_id) + trade_two = self.app.algorithm.get_trade(order_id=order_two_id) + self.assertEqual(2.5, trade_one.remaining) + self.assertEqual(5, trade_two.remaining) self.assertEqual(2, len(self.app.algorithm.get_open_trades("BTC"))) self.app.algorithm.create_limit_order( target_symbol="BTC", @@ -180,9 +182,15 @@ def test_get_open_trades_with_close_trades_of_partial_buy_orders(self): amount=5 ) trades = self.app.algorithm.get_open_trades() - self.assertEqual(1, len(trades)) - self.assertEqual(2.5, trades[0].amount) - self.app.algorithm.check_pending_orders() + self.assertEqual(2, len(trades)) + trade_one = self.app.algorithm.get_trade(order_id=order_one_id) + trade_two = self.app.algorithm.get_trade(order_id=order_two_id) + self.assertEqual(2.5, trade_one.remaining) + self.assertEqual(5, trade_two.remaining) + self.app.algorithm.order_service.check_pending_orders() trades = self.app.algorithm.get_open_trades() self.assertEqual(1, len(trades)) - self.assertEqual(2.5, trades[0].amount) + trade_one = self.app.algorithm.get_trade(order_id=order_one_id) + trade_two = self.app.algorithm.get_trade(order_id=order_two_id) + self.assertEqual(0, trade_one.remaining) + self.assertEqual(2.5, trade_two.remaining) diff --git a/tests/app/algorithm/test_get_order.py b/tests/app/algorithm/test_get_order.py index 55db9043..2ca7c24e 100644 --- a/tests/app/algorithm/test_get_order.py +++ b/tests/app/algorithm/test_get_order.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ OrderStatus, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,15 +23,15 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_create_limit_buy_order_with_percentage_of_portfolio(self): - self.app.algorithm.create_limit_order( + order = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=20 ) - order = self.app.algorithm.get_order() self.assertEqual(OrderStatus.OPEN.value, order.status) self.assertEqual(Decimal(10), order.get_price()) self.assertEqual(Decimal(20), order.get_amount()) diff --git a/tests/app/algorithm/test_get_pending_orders.py b/tests/app/algorithm/test_get_pending_orders.py index 559d30a3..ec765d35 100644 --- a/tests/app/algorithm/test_get_pending_orders.py +++ b/tests/app/algorithm/test_get_pending_orders.py @@ -1,7 +1,7 @@ from investing_algorithm_framework import PortfolioConfiguration, Order, \ - MarketCredential, SYMBOLS + MarketCredential from investing_algorithm_framework.services import PortfolioService -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class TestPortfolioService(TestBase): @@ -68,6 +68,7 @@ class TestPortfolioService(TestBase): external_balances = { "EUR": 700, } + market_data_source_service = MarketDataSourceServiceStub() def test_get_pending_orders(self): """ @@ -101,7 +102,7 @@ def test_get_pending_orders(self): # Check that the portfolio has the correct amount of trades trade_service = self.app.container.trade_service() - self.assertEqual(1, trade_service.count()) + self.assertEqual(3, trade_service.count()) self.assertEqual( 1, trade_service.count( {"portfolio_id": portfolio.id, "status": "OPEN"} diff --git a/tests/app/algorithm/test_get_portfolio.py b/tests/app/algorithm/test_get_portfolio.py index a669d60c..6043646b 100644 --- a/tests/app/algorithm/test_get_portfolio.py +++ b/tests/app/algorithm/test_get_portfolio.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_get_portfolio(self): portfolio = self.app.algorithm.get_portfolio() diff --git a/tests/app/algorithm/test_get_position.py b/tests/app/algorithm/test_get_position.py index 189c6e2a..ff03272b 100644 --- a/tests/app/algorithm/test_get_position.py +++ b/tests/app/algorithm/test_get_position.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_get_position(self): trading_symbol_position = self.app.algorithm.get_position("EUR") diff --git a/tests/app/algorithm/test_get_trades.py b/tests/app/algorithm/test_get_trades.py index 2b918430..358b80d3 100644 --- a/tests/app/algorithm/test_get_trades.py +++ b/tests/app/algorithm/test_get_trades.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ CSVTickerMarketDataSource, MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super(Test, self).setUp() @@ -33,20 +34,19 @@ def setUp(self) -> None: csv_file_path=os.path.join( self.resource_directory, "market_data_sources_for_testing", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" ) )) def test_get_trades(self): - self.app.algorithm.create_limit_order( + order = self.app.algorithm.create_limit_order( target_symbol="BTC", price=10, order_side="BUY", amount=20 ) - order = self.app.algorithm.get_order() self.assertIsNotNone(order) - self.assertEqual(0, len(self.app.algorithm.get_trades())) + self.assertEqual(1, len(self.app.algorithm.get_trades())) order_service = self.app.container.order_service() order_service.check_pending_orders() self.assertEqual(1, len(self.app.algorithm.get_trades())) @@ -55,7 +55,6 @@ def test_get_trades(self): self.assertEqual(20, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNone(trade.closed_price) self.assertIsNone(trade.closed_at) self.app.algorithm.create_limit_order( target_symbol="BTC", @@ -70,5 +69,4 @@ def test_get_trades(self): self.assertEqual(20, trade.amount) self.assertEqual("BTC", trade.target_symbol) self.assertEqual("EUR", trade.trading_symbol) - self.assertIsNotNone(trade.closed_price) self.assertIsNotNone(trade.closed_at) diff --git a/tests/app/algorithm/test_get_unallocated.py b/tests/app/algorithm/test_get_unallocated.py index 07d4e8a8..cd74279b 100644 --- a/tests/app/algorithm/test_get_unallocated.py +++ b/tests/app/algorithm/test_get_unallocated.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -23,6 +23,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_create_limit_buy_order_with_percentage_of_portfolio(self): portfolio = self.app.algorithm.get_portfolio() diff --git a/tests/app/algorithm/test_get_unfilled_buy_value.py b/tests/app/algorithm/test_get_unfilled_buy_value.py index 806745b4..a03fda54 100644 --- a/tests/app/algorithm/test_get_unfilled_buy_value.py +++ b/tests/app/algorithm/test_get_unfilled_buy_value.py @@ -1,7 +1,7 @@ from investing_algorithm_framework import PortfolioConfiguration, Order, \ - MarketCredential, SYMBOLS + MarketCredential from investing_algorithm_framework.services import PortfolioService -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -71,6 +71,7 @@ class Test(TestBase): secret_key="secret_key" ) ] + market_data_source_service = MarketDataSourceServiceStub() def test_get_unfilled_buy_value(self): """ @@ -112,7 +113,7 @@ def test_get_unfilled_buy_value(self): # Check that the portfolio has the correct amount of trades trade_service = self.app.container.trade_service() - self.assertEqual(1, trade_service.count()) + self.assertEqual(3, trade_service.count()) self.assertEqual( 1, trade_service.count( {"portfolio_id": portfolio.id, "status": "OPEN"} diff --git a/tests/app/algorithm/test_get_unfilled_sell_value.py b/tests/app/algorithm/test_get_unfilled_sell_value.py index 96940771..29e4256b 100644 --- a/tests/app/algorithm/test_get_unfilled_sell_value.py +++ b/tests/app/algorithm/test_get_unfilled_sell_value.py @@ -1,7 +1,7 @@ from investing_algorithm_framework import PortfolioConfiguration, Order, \ - MarketCredential, SYMBOLS + MarketCredential from investing_algorithm_framework.services import PortfolioService -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -114,6 +114,7 @@ class Test(TestBase): external_balances = { "EUR": 1000 } + market_data_source_service = MarketDataSourceServiceStub() def test_get_unfilled_sell_value(self): """ diff --git a/tests/app/algorithm/test_has_open_buy_orders.py b/tests/app/algorithm/test_has_open_buy_orders.py index dfc236a4..7c7051c5 100644 --- a/tests/app/algorithm/test_has_open_buy_orders.py +++ b/tests/app/algorithm/test_has_open_buy_orders.py @@ -2,7 +2,7 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -24,6 +24,7 @@ class Test(TestBase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def test_has_open_buy_orders(self): trading_symbol_position = self.app.algorithm.get_position("EUR") diff --git a/tests/app/algorithm/test_has_open_sell_orders.py b/tests/app/algorithm/test_has_open_sell_orders.py index 196e1059..94429f2f 100644 --- a/tests/app/algorithm/test_has_open_sell_orders.py +++ b/tests/app/algorithm/test_has_open_sell_orders.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -22,6 +22,7 @@ class Test(TestBase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def test_has_open_sell_orders(self): trading_symbol_position = self.app.algorithm.get_position("EUR") diff --git a/tests/app/algorithm/test_has_position.py b/tests/app/algorithm/test_has_position.py index 49aa4ead..da00d7a9 100644 --- a/tests/app/algorithm/test_has_position.py +++ b/tests/app/algorithm/test_has_position.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -22,6 +22,7 @@ class Test(TestBase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def test_has_position(self): trading_symbol_position = self.app.algorithm.get_position("EUR") diff --git a/tests/app/algorithm/test_has_trading_symbol_position_available.py b/tests/app/algorithm/test_has_trading_symbol_position_available.py index bb732228..dc7397c6 100644 --- a/tests/app/algorithm/test_has_trading_symbol_position_available.py +++ b/tests/app/algorithm/test_has_trading_symbol_position_available.py @@ -1,6 +1,6 @@ from investing_algorithm_framework import PortfolioConfiguration, \ MarketCredential -from tests.resources import TestBase +from tests.resources import TestBase, MarketDataSourceServiceStub class Test(TestBase): @@ -22,6 +22,7 @@ class Test(TestBase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def test_has_trading_symbol_available(self): self.assertTrue( diff --git a/tests/app/algorithm/test_round_down.py b/tests/app/algorithm/test_round_down.py index 85d3b6da..a862786e 100644 --- a/tests/app/algorithm/test_round_down.py +++ b/tests/app/algorithm/test_round_down.py @@ -1,12 +1,10 @@ import os +from unittest import TestCase -from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY, \ - PortfolioConfiguration, Algorithm, MarketCredential from investing_algorithm_framework.domain import RoundingService -from tests.resources import TestBase, MarketServiceStub -class Test(TestBase): +class Test(TestCase): def count_decimals(self, number): decimal_str = str(number) @@ -15,40 +13,6 @@ def count_decimals(self, number): else: return 0 - def setUp(self) -> None: - self.resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - os.pardir - ), - "resources" - ) - ) - self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) - self.app.add_portfolio_configuration( - PortfolioConfiguration( - market="binance", - trading_symbol="EUR" - ) - ) - self.app.container.market_service.override(MarketServiceStub(None)) - self.app.add_algorithm(Algorithm()) - self.app.add_market_credential( - MarketCredential( - market="binance", - api_key="api_key", - secret_key="secret_key" - ) - ) - self.app.initialize() - def test_round_down(self): new_value = RoundingService.round_down(1, 3) self.assertEqual( diff --git a/tests/app/algorithm/test_run_strategy.py b/tests/app/algorithm/test_run_strategy.py index 07262d9b..658737c1 100644 --- a/tests/app/algorithm/test_run_strategy.py +++ b/tests/app/algorithm/test_run_strategy.py @@ -4,7 +4,8 @@ from investing_algorithm_framework import create_app, TradingStrategy, \ TimeUnit, PortfolioConfiguration, RESOURCE_DIRECTORY, \ Algorithm, MarketCredential -from tests.resources import random_string, MarketServiceStub +from tests.resources import random_string, MarketServiceStub, \ + MarketDataSourceServiceStub class StrategyOne(TradingStrategy): @@ -41,6 +42,7 @@ class Test(TestCase): external_balances = { "EUR": 1000, } + market_data_source_service = MarketDataSourceServiceStub() def setUp(self) -> None: super(Test, self).setUp() diff --git a/tests/app/algorithm/test_trade_price_update.py b/tests/app/algorithm/test_trade_price_update.py new file mode 100644 index 00000000..074df5d0 --- /dev/null +++ b/tests/app/algorithm/test_trade_price_update.py @@ -0,0 +1,130 @@ +import os +from datetime import datetime, timezone +from unittest import TestCase + +from investing_algorithm_framework import create_app, TradingStrategy, \ + TimeUnit, PortfolioConfiguration, RESOURCE_DIRECTORY, \ + Algorithm, MarketCredential, CSVOHLCVMarketDataSource, \ + CSVTickerMarketDataSource +from tests.resources import random_string, MarketServiceStub, \ + MarketDataSourceServiceStub + + +class StrategyOne(TradingStrategy): + time_unit = TimeUnit.SECOND + interval = 2 + market_data_sources = ["BTC/EUR-ohlcv", "BTC/EUR-ticker"] + + def apply_strategy(self, algorithm, market_data): + pass + + +class StrategyTwo(TradingStrategy): + time_unit = TimeUnit.SECOND + interval = 2 + market_data_sources = ["BTC/EUR-ohlcv", "BTC/EUR-ticker"] + + def apply_strategy(self, algorithm, market_data): + pass + + +class Test(TestCase): + + def setUp(self) -> None: + self.resource_dir = os.path.abspath( + os.path.join( + os.path.join( + os.path.join( + os.path.join( + os.path.realpath(__file__), + os.pardir + ), + os.pardir + ), + os.pardir + ), + "resources" + ) + ) + + def tearDown(self) -> None: + super().tearDown() + # Delete the resources database directory + + database_dir = os.path.join(self.resource_dir, "databases") + + if os.path.exists(database_dir): + for root, dirs, files in os.walk(database_dir, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + + + def test_trade_recent_price_update(self): + app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) + app.container.market_service.override(MarketServiceStub(None)) + app.container.portfolio_configuration_service().clear() + app.add_market_data_source( + CSVOHLCVMarketDataSource( + identifier="BTC/EUR-ohlcv", + market="BINANCE", + symbol="BTC/EUR", + window_size=200, + csv_file_path=os.path.join( + self.resource_dir, + "market_data_sources", + "OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv" + ) + ) + ) + app.add_market_data_source( + CSVTickerMarketDataSource( + identifier="BTC/EUR-ticker", + market="BINANCE", + symbol="BTC/EUR", + csv_file_path=os.path.join( + self.resource_dir, + "market_data_sources", + "TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" + ) + ) + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BINANCE", + trading_symbol="EUR", + ) + ) + app.add_market_credential( + MarketCredential( + market="BINANCE", + api_key=random_string(10), + secret_key=random_string(10) + ) + ) + algorithm = Algorithm() + algorithm.add_strategy(StrategyOne) + algorithm.add_strategy(StrategyTwo) + app.add_algorithm(algorithm) + app.set_config( + "DATE_TIME", datetime(2023, 11, 2, 7, 59, tzinfo=timezone.utc) + ) + app.initialize_config() + app.initialize() + app.algorithm.create_limit_order( + target_symbol="btc", + amount=20, + price=20, + order_side="BUY" + ) + app.run(number_of_iterations=1) + strategy_orchestration_service = app.algorithm\ + .strategy_orchestrator_service + self.assertTrue(strategy_orchestration_service.has_run("StrategyOne")) + self.assertTrue(strategy_orchestration_service.has_run("StrategyTwo")) + + # Check that the last reported price is updated + trade = app.algorithm.get_trades()[0] + self.assertIsNotNone(trade) + self.assertIsNotNone(trade.last_reported_price) diff --git a/tests/app/backtesting/test_run_backtests.py b/tests/app/backtesting/test_run_backtests.py index b59ebdd9..41b46d3b 100644 --- a/tests/app/backtesting/test_run_backtests.py +++ b/tests/app/backtesting/test_run_backtests.py @@ -76,10 +76,6 @@ def test_run_backtests(self): start_date=start_date, end_date=end_date ) - app._initialize_app_for_backtest( - backtest_date_range=backtest_date_range, - pending_order_check_interval='2h', - ) reports = app.run_backtests( algorithms=[algorithm_one, algorithm_two, algorithm_three], date_ranges=[backtest_date_range] diff --git a/tests/app/test_add_config.py b/tests/app/test_add_config.py index 9c3c32a3..4b43b489 100644 --- a/tests/app/test_add_config.py +++ b/tests/app/test_add_config.py @@ -1,29 +1,29 @@ -# import os -# from unittest import TestCase +import os +from unittest import TestCase -# from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY +from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY -# class Test(TestCase): +class Test(TestCase): -# def setUp(self) -> None: -# self.resource_dir = os.path.abspath( -# os.path.join( -# os.path.join( -# os.path.join( -# os.path.realpath(__file__), -# os.pardir -# ), -# os.pardir -# ), -# "resources" -# ) -# ) + def setUp(self) -> None: + self.resource_dir = os.path.abspath( + os.path.join( + os.path.join( + os.path.join( + os.path.realpath(__file__), + os.pardir + ), + os.pardir + ), + "resources" + ) + ) -# def test_add(self): -# app = create_app( -# config={"test": "test", RESOURCE_DIRECTORY: self.resource_dir} -# ) -# self.assertIsNotNone(app.config) -# self.assertIsNotNone(app.config.get("test")) -# self.assertIsNotNone(app.config.get(RESOURCE_DIRECTORY)) + def test_add(self): + app = create_app( + config={"test": "test", RESOURCE_DIRECTORY: self.resource_dir} + ) + self.assertIsNotNone(app.config) + self.assertIsNotNone(app.config.get("test")) + self.assertIsNotNone(app.config.get(RESOURCE_DIRECTORY)) diff --git a/tests/app/test_add_portfolio_configuration.py b/tests/app/test_add_portfolio_configuration.py index a71c2ab2..d9bb7cbc 100644 --- a/tests/app/test_add_portfolio_configuration.py +++ b/tests/app/test_add_portfolio_configuration.py @@ -1,31 +1,31 @@ -# from investing_algorithm_framework import PortfolioConfiguration, \ -# MarketCredential -# from tests.resources import TestBase +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential +from tests.resources import TestBase -# class Test(TestBase): -# portfolio_configurations = [ -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR" -# ) -# ] -# market_credentials = [ -# MarketCredential( -# market="BITVAVO", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ] -# external_balances = { -# "EUR": 1000, -# } +class Test(TestBase): + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ] + external_balances = { + "EUR": 1000, + } -# def test_add(self): -# self.assertEqual(1, self.app.algorithm.portfolio_service.count()) -# self.assertEqual(1, self.app.algorithm.position_service.count()) -# self.assertEqual(1000, self.app.algorithm.get_unallocated()) + def test_add(self): + self.assertEqual(1, self.app.algorithm.portfolio_service.count()) + self.assertEqual(1, self.app.algorithm.position_service.count()) + self.assertEqual(1000, self.app.algorithm.get_unallocated()) -# # Make sure that the portfolio is initialized -# portfolio = self.app.algorithm.get_portfolio() -# self.assertTrue(portfolio.initialized) + # Make sure that the portfolio is initialized + portfolio = self.app.algorithm.get_portfolio() + self.assertTrue(portfolio.initialized) diff --git a/tests/app/test_app_initialize.py b/tests/app/test_app_initialize.py index 66b5624f..9316ded2 100644 --- a/tests/app/test_app_initialize.py +++ b/tests/app/test_app_initialize.py @@ -1,97 +1,99 @@ -# import os -# from unittest import TestCase +import os +from unittest import TestCase -# from investing_algorithm_framework import create_app, PortfolioConfiguration, \ -# MarketCredential, Algorithm, AppMode, APP_MODE, RESOURCE_DIRECTORY -# from investing_algorithm_framework.domain import SQLALCHEMY_DATABASE_URI -# from tests.resources import MarketServiceStub +from investing_algorithm_framework import create_app, PortfolioConfiguration, \ + MarketCredential, Algorithm, AppMode, APP_MODE, RESOURCE_DIRECTORY +from investing_algorithm_framework.domain import SQLALCHEMY_DATABASE_URI +from tests.resources import MarketServiceStub -# class TestAppInitialize(TestCase): -# portfolio_configurations = [ -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR" -# ) -# ] -# market_credentials = [ -# MarketCredential( -# market="BITVAVO", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ] -# external_balances = { -# "EUR": 1000, -# } +class TestAppInitialize(TestCase): + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ] + external_balances = { + "EUR": 1000, + } -# def setUp(self) -> None: -# self.resource_dir = os.path.abspath( -# os.path.join( -# os.path.join( -# os.path.join( -# os.path.realpath(__file__), -# os.pardir -# ), -# os.pardir -# ), -# "resources" -# ) -# ) + def setUp(self) -> None: + self.resource_dir = os.path.abspath( + os.path.join( + os.path.join( + os.path.join( + os.path.realpath(__file__), + os.pardir + ), + os.pardir + ), + "resources" + ) + ) -# def test_app_initialize_default(self): -# app = create_app( -# config={RESOURCE_DIRECTORY: self.resource_dir} -# ) -# app.container.market_service.override( -# MarketServiceStub(app.container.market_credential_service()) -# ) -# app.add_portfolio_configuration( -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR", -# ) -# ) -# algorithm = Algorithm() -# app.add_algorithm(algorithm) -# app.add_market_credential( -# MarketCredential( -# market="BITVAVO", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ) -# app.initialize() -# self.assertIsNotNone(app.config) -# self.assertIsNone(app._flask_app) -# self.assertTrue(AppMode.DEFAULT.equals(app.config[APP_MODE])) -# order_service = app.container.order_service() -# self.assertEqual(0, order_service.count()) + def test_app_initialize_default(self): + app = create_app( + config={RESOURCE_DIRECTORY: self.resource_dir} + ) + app.container.market_service.override( + MarketServiceStub(app.container.market_credential_service()) + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR", + ) + ) + algorithm = Algorithm() + app.add_algorithm(algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + app.initialize_config() + app.initialize() + self.assertIsNotNone(app.config) + self.assertIsNone(app._flask_app) + self.assertTrue(AppMode.DEFAULT.equals(app.config[APP_MODE])) + order_service = app.container.order_service() + self.assertEqual(0, order_service.count()) -# def test_app_initialize_web(self): -# app = create_app( -# config={RESOURCE_DIRECTORY: self.resource_dir}, -# web=True -# ) -# app.container.market_service.override(MarketServiceStub(None)) -# app.add_portfolio_configuration( -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR", -# ) -# ) -# algorithm = Algorithm() -# app.add_algorithm(algorithm) -# app.add_market_credential( -# MarketCredential( -# market="BITVAVO", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ) -# app.initialize() -# self.assertIsNotNone(app.config) -# self.assertIsNotNone(app._flask_app) -# self.assertTrue(AppMode.WEB.equals(app.config[APP_MODE])) -# order_service = app.container.order_service() -# self.assertEqual(0, order_service.count()) + def test_app_initialize_web(self): + app = create_app( + config={RESOURCE_DIRECTORY: self.resource_dir}, + web=True + ) + app.container.market_service.override(MarketServiceStub(None)) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR", + ) + ) + algorithm = Algorithm() + app.add_algorithm(algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + app.initialize_config() + app.initialize() + self.assertIsNotNone(app.config) + self.assertIsNotNone(app._flask_app) + self.assertTrue(AppMode.WEB.equals(app.config[APP_MODE])) + order_service = app.container.order_service() + self.assertEqual(0, order_service.count()) diff --git a/tests/app/test_config.py b/tests/app/test_config.py index 02e10ae7..249f6dec 100644 --- a/tests/app/test_config.py +++ b/tests/app/test_config.py @@ -1,25 +1,104 @@ -# from unittest import TestCase +import os -# from investing_algorithm_framework import create_app -# from investing_algorithm_framework.domain import RESOURCE_DIRECTORY -# from tests.resources import random_string +from investing_algorithm_framework import create_app, Algorithm, \ + PortfolioConfiguration, MarketCredential +from investing_algorithm_framework.domain import RESOURCE_DIRECTORY, \ + DATABASE_DIRECTORY_NAME, DATABASE_DIRECTORY_PATH, DATABASE_NAME, \ + ENVIRONMENT, Environment, BacktestDateRange +from tests.resources import random_string, TestBase -# TEST_VALUE = random_string(10) +TEST_VALUE = random_string(10) -# class TestConfig(TestCase): -# ATTRIBUTE_ONE = "ATTRIBUTE_ONE" -# def test_config(self): -# app = create_app( -# config={self.ATTRIBUTE_ONE: self.ATTRIBUTE_ONE} -# ) -# app.initialize_config() -# self.assertIsNotNone(app.config) -# self.assertIsNotNone(app.config[self.ATTRIBUTE_ONE]) +class TestConfig(TestBase): + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ] + external_balances = { + "EUR": 1000, + } + ATTRIBUTE_ONE = "ATTRIBUTE_ONE" -# def test_resource_directory_exists(self): -# app = create_app() -# app.initialize_config() -# self.assertIsNotNone(app.config) -# self.assertIsNotNone(app.config[RESOURCE_DIRECTORY]) + def test_config(self): + self.app.set_config(self.ATTRIBUTE_ONE, TEST_VALUE) + self.app.initialize_config() + self.assertIsNotNone(self.app.config) + self.assertIsNotNone(self.app.config[self.ATTRIBUTE_ONE]) + + def test_resource_directory_exists(self): + self.app.set_config(self.ATTRIBUTE_ONE, TEST_VALUE) + self.app.initialize_config() + self.assertIsNotNone(self.app.config) + self.assertIsNotNone(self.app.config[RESOURCE_DIRECTORY]) + + def test_config_production(self): + self.app.set_config(ENVIRONMENT, Environment.PROD.value) + self.app.initialize_config() + self.app.initialize() + self.assertIsNotNone(self.app.config[RESOURCE_DIRECTORY]) + self.assertIsNotNone(self.app.config[ENVIRONMENT]) + self.assertEqual( + self.app.config[ENVIRONMENT], Environment.PROD.value + ) + self.assertIsNotNone(self.app.config[DATABASE_DIRECTORY_NAME]) + self.assertEqual( + "databases", self.app.config[DATABASE_DIRECTORY_NAME] + ) + self.assertIsNotNone(self.app.config[DATABASE_NAME]) + self.assertEqual( + "prod-database.sqlite3", self.app.config[DATABASE_NAME] + ) + self.assertIsNotNone(self.app.config[DATABASE_DIRECTORY_NAME]) + database_directory_path = os.path.join( + self.app.config[RESOURCE_DIRECTORY], + self.app.config[DATABASE_DIRECTORY_NAME] + ) + self.assertEqual( + database_directory_path, self.app.config[DATABASE_DIRECTORY_PATH] + ) + + def test_config_backtest(self): + date_range = BacktestDateRange( + start_date="2021-01-01", + end_date="2021-01-01 00:30:00" + ) + + @self.app.algorithm.strategy(interval=1, time_unit="SECOND") + def test_strategy(algorithm, market_data): + pass + + self.app.run_backtest( + backtest_date_range=date_range, initial_amount=1000 + ) + self.assertIsNotNone(self.app.config[RESOURCE_DIRECTORY]) + self.assertIsNotNone(self.app.config[ENVIRONMENT]) + self.assertEqual( + self.app.config[ENVIRONMENT], Environment.BACKTEST.value + ) + self.assertIsNotNone(self.app.config[DATABASE_DIRECTORY_NAME]) + self.assertEqual( + "backtest_databases", self.app.config[DATABASE_DIRECTORY_NAME] + ) + self.assertIsNotNone(self.app.config[DATABASE_NAME]) + self.assertEqual( + "backtest-database.sqlite3", self.app.config[DATABASE_NAME] + ) + self.assertIsNotNone(self.app.config[DATABASE_DIRECTORY_NAME]) + database_directory_path = os.path.join( + self.app.config[RESOURCE_DIRECTORY], + self.app.config[DATABASE_DIRECTORY_NAME] + ) + self.assertEqual( + database_directory_path, self.app.config[DATABASE_DIRECTORY_PATH] + ) diff --git a/tests/app/test_start.py b/tests/app/test_start.py index 5b5bba0e..114e6a59 100644 --- a/tests/app/test_start.py +++ b/tests/app/test_start.py @@ -1,95 +1,224 @@ -# import os - -# from investing_algorithm_framework import create_app, TradingStrategy, \ -# TimeUnit, RESOURCE_DIRECTORY, PortfolioConfiguration, Algorithm, \ -# MarketCredential -# from tests.resources import TestBase, MarketServiceStub - - -# class StrategyOne(TradingStrategy): -# time_unit = TimeUnit.SECOND -# interval = 2 - -# def apply_strategy( -# self, -# algorithm, -# market_date=None, -# **kwargs -# ): -# pass - - -# class StrategyTwo(TradingStrategy): -# time_unit = TimeUnit.SECOND -# interval = 2 - -# def apply_strategy( -# self, -# algorithm, -# market_date=None, -# **kwargs -# ): -# pass - - -# class Test(TestBase): - -# def setUp(self) -> None: -# self.resource_dir = os.path.abspath( -# os.path.join( -# os.path.join( -# os.path.join( -# os.path.realpath(__file__), -# os.pardir -# ), -# os.pardir -# ), -# "resources" -# ) -# ) -# self.app = create_app(config={RESOURCE_DIRECTORY: self.resource_dir}) -# self.app.add_portfolio_configuration( -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR" -# ) -# ) - -# self.app.container.market_service.override(MarketServiceStub(None)) -# algorithm = Algorithm() -# algorithm.add_strategy(StrategyOne) -# algorithm.add_strategy(StrategyTwo) -# self.app.add_algorithm(algorithm) -# self.app.add_market_credential( -# MarketCredential( -# market="BITVAVO", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ) - -# def test_default(self): -# self.app.run(number_of_iterations=2) -# self.assertFalse(self.app.running) -# strategy_orchestrator_service = self.app \ -# .algorithm.strategy_orchestrator_service -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) - -# def test_web(self): -# self.app.run(number_of_iterations=2) -# self.assertFalse(self.app.running) -# strategy_orchestrator_service = self.app \ -# .algorithm.strategy_orchestrator_service -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) - -# def test_stateless(self): -# self.app.run( -# number_of_iterations=2, -# payload={"ACTION": "RUN_STRATEGY"}, -# ) -# strategy_orchestrator_service = self.app\ -# .algorithm.strategy_orchestrator_service -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) -# self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) +import os +from unittest import TestCase + +from investing_algorithm_framework import create_app, TradingStrategy, \ + TimeUnit, RESOURCE_DIRECTORY, PortfolioConfiguration, Algorithm, \ + MarketCredential +from tests.resources import TestBase, MarketServiceStub + + +class StrategyOne(TradingStrategy): + time_unit = TimeUnit.SECOND + interval = 2 + number_of_runs = 0 + + def __init__( + self, + strategy_id=None, + time_unit=None, + interval=None, + market_data_sources=None, + worker_id=None, + decorated=None + ): + super().__init__( + strategy_id, + time_unit, + interval, + market_data_sources, + worker_id, + decorated + ) + StrategyOne.number_of_runs = 0 + + def apply_strategy( + self, + algorithm, + market_date=None, + **kwargs + ): + StrategyOne.number_of_runs += 1 + + +class StrategyTwo(TradingStrategy): + time_unit = TimeUnit.SECOND + interval = 2 + number_of_runs = 0 + + def __init__( + self, + strategy_id=None, + time_unit=None, + interval=None, + market_data_sources=None, + worker_id=None, + decorated=None + ): + super().__init__( + strategy_id, + time_unit, + interval, + market_data_sources, + worker_id, + decorated + ) + StrategyOne.number_of_runs = 0 + + def apply_strategy( + self, + algorithm, + market_date=None, + **kwargs + ): + StrategyTwo.number_of_runs += 1 + + +class Test(TestCase): + + def setUp(self) -> None: + self.resource_dir = os.path.abspath( + os.path.join( + os.path.join( + os.path.join( + os.path.realpath(__file__), + os.pardir + ), + os.pardir + ), + "resources" + ) + ) + + def tearDown(self): + paths = [ + os.path.join( + self.resource_dir, "databases" + ), + os.path.join( + self.resource_dir, "backtest_databases" + ) + ] + + for path in paths: + self._remove_directory(path) + + def _remove_directory(self, path): + if os.path.exists(path): + for root, dirs, files in os.walk(path, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + + def test_default(self): + app = create_app({ + RESOURCE_DIRECTORY: self.resource_dir + }) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ) + app.container.market_service.override(MarketServiceStub(None)) + algorithm = Algorithm() + algorithm.add_strategy(StrategyOne) + algorithm.add_strategy(StrategyTwo) + app.add_algorithm(algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + market_service_stub = MarketServiceStub(None) + market_service_stub.balances = { + "EUR": 1000 + } + app.container.market_service.override(market_service_stub) + # while app.running: + # time.sleep(1) + app.run(number_of_iterations=2) + # self.assertEqual(2, StrategyOne.number_of_runs) + # self.assertEqual(2, StrategyTwo.number_of_runs) + self.assertFalse(app.running) + strategy_orchestrator_service = app \ + .algorithm.strategy_orchestrator_service + self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) + self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) + + def test_web(self): + app = create_app( + web=True, + config={ RESOURCE_DIRECTORY: self.resource_dir } + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ) + app.container.market_service.override(MarketServiceStub(None)) + algorithm = Algorithm() + algorithm.add_strategy(StrategyOne) + algorithm.add_strategy(StrategyTwo) + app.add_algorithm(algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + market_service_stub = MarketServiceStub(None) + market_service_stub.balances = { + "EUR": 1000 + } + app.container.market_service.override(market_service_stub) + app.run(number_of_iterations=2) + # self.assertEqual(2, StrategyOne.number_of_runs) + # self.assertEqual(2, StrategyTwo.number_of_runs) + self.assertFalse(app.running) + strategy_orchestrator_service = app \ + .algorithm.strategy_orchestrator_service + self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) + self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) + + def test_with_payload(self): + app = create_app( + config={ RESOURCE_DIRECTORY: self.resource_dir } + ) + app.add_portfolio_configuration( + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ) + app.container.market_service.override(MarketServiceStub(None)) + algorithm = Algorithm() + algorithm.add_strategy(StrategyOne) + algorithm.add_strategy(StrategyTwo) + app.add_algorithm(algorithm) + app.add_market_credential( + MarketCredential( + market="BITVAVO", + api_key="api_key", + secret_key="secret_key" + ) + ) + market_service_stub = MarketServiceStub(None) + market_service_stub.balances = { + "EUR": 1000 + } + app.container.market_service.override(market_service_stub) + app.run( + number_of_iterations=2, + payload={"ACTION": "RUN_STRATEGY"}, + ) + # self.assertEqual(2, StrategyOne.number_of_runs) + # self.assertEqual(2, StrategyTwo.number_of_runs) + strategy_orchestrator_service = app\ + .algorithm.strategy_orchestrator_service + self.assertTrue(strategy_orchestrator_service.has_run("StrategyOne")) + self.assertTrue(strategy_orchestrator_service.has_run("StrategyTwo")) diff --git a/tests/app/test_start_with_new_external_orders.py b/tests/app/test_start_with_new_external_orders.py index 69f98538..32038d03 100644 --- a/tests/app/test_start_with_new_external_orders.py +++ b/tests/app/test_start_with_new_external_orders.py @@ -1,132 +1,132 @@ -# from investing_algorithm_framework import Order, PortfolioConfiguration, \ -# MarketCredential -# from tests.resources import TestBase +from investing_algorithm_framework import Order, PortfolioConfiguration, \ + MarketCredential +from tests.resources import TestBase -# class Test(TestBase): -# initial_orders = [ -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "BTC/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# Order.from_dict( -# { -# "id": "14354", -# "side": "buy", -# "symbol": "DOT/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-09-22T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "ETH/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "OPEN", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 0, -# "remaining": 0, -# }, -# ), -# ] -# external_orders = [ -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "ETH/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# # Order that is not tracked by the trading bot -# Order.from_dict( -# { -# "id": "133423", -# "side": "buy", -# "symbol": "KSM/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# ] -# external_balances = {"EUR": 1000} -# portfolio_configurations = [ -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR" -# ) -# ] -# market_credentials = [ -# MarketCredential( -# market="bitvavo", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ] +class Test(TestBase): + initial_orders = [ + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "BTC/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + Order.from_dict( + { + "id": "14354", + "side": "buy", + "symbol": "DOT/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-09-22T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "ETH/EUR", + "amount": 10, + "price": 10.0, + "status": "OPEN", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 0, + "remaining": 0, + }, + ), + ] + external_orders = [ + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "ETH/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + # Order that is not tracked by the trading bot + Order.from_dict( + { + "id": "133423", + "side": "buy", + "symbol": "KSM/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + ] + external_balances = {"EUR": 1000} + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="bitvavo", + api_key="api_key", + secret_key="secret_key" + ) + ] -# def test_start_with_new_external_positions(self): -# """ -# Test how the framework handles new external positions on an broker or -# exchange. + def test_start_with_new_external_positions(self): + """ + Test how the framework handles new external positions on an broker or + exchange. -# If the positions where not in the database, the algorithm should -# not include them, because they could be positions from another -# user or from another algorithm. -# """ -# self.assertTrue(self.app.algorithm.has_position("BTC")) -# btc_position = self.app.algorithm.get_position("BTC") -# self.assertEqual(10, btc_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("DOT")) -# dot_position = self.app.algorithm.get_position("DOT") -# self.assertEqual(10, dot_position.get_amount()) -# # Eth position still has open order -# self.assertFalse(self.app.algorithm.has_position("ETH")) -# eth_position = self.app.algorithm.get_position("ETH") -# self.assertEqual(0, eth_position.get_amount()) -# self.assertFalse(self.app.algorithm.has_position("KSM")) -# self.app.run(number_of_iterations=1) -# self.assertTrue(self.app.algorithm.has_position("BTC")) -# btc_position = self.app.algorithm.get_position("BTC") -# self.assertEqual(10, btc_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("DOT")) -# dot_position = self.app.algorithm.get_position("DOT") -# self.assertEqual(10, dot_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("ETH")) -# eth_position = self.app.algorithm.get_position("ETH") -# self.assertEqual(10, eth_position.get_amount()) -# self.assertFalse(self.app.algorithm.has_position("KSM")) + If the positions where not in the database, the algorithm should + not include them, because they could be positions from another + user or from another algorithm. + """ + self.assertTrue(self.app.algorithm.has_position("BTC")) + btc_position = self.app.algorithm.get_position("BTC") + self.assertEqual(10, btc_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("DOT")) + dot_position = self.app.algorithm.get_position("DOT") + self.assertEqual(10, dot_position.get_amount()) + # Eth position still has open order + self.assertFalse(self.app.algorithm.has_position("ETH")) + eth_position = self.app.algorithm.get_position("ETH") + self.assertEqual(0, eth_position.get_amount()) + self.assertFalse(self.app.algorithm.has_position("KSM")) + self.app.run(number_of_iterations=1) + self.assertTrue(self.app.algorithm.has_position("BTC")) + btc_position = self.app.algorithm.get_position("BTC") + self.assertEqual(10, btc_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("DOT")) + dot_position = self.app.algorithm.get_position("DOT") + self.assertEqual(10, dot_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("ETH")) + eth_position = self.app.algorithm.get_position("ETH") + self.assertEqual(10, eth_position.get_amount()) + self.assertFalse(self.app.algorithm.has_position("KSM")) diff --git a/tests/app/test_start_with_new_external_positions.py b/tests/app/test_start_with_new_external_positions.py index 69f98538..32038d03 100644 --- a/tests/app/test_start_with_new_external_positions.py +++ b/tests/app/test_start_with_new_external_positions.py @@ -1,132 +1,132 @@ -# from investing_algorithm_framework import Order, PortfolioConfiguration, \ -# MarketCredential -# from tests.resources import TestBase +from investing_algorithm_framework import Order, PortfolioConfiguration, \ + MarketCredential +from tests.resources import TestBase -# class Test(TestBase): -# initial_orders = [ -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "BTC/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# Order.from_dict( -# { -# "id": "14354", -# "side": "buy", -# "symbol": "DOT/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-09-22T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "ETH/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "OPEN", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 0, -# "remaining": 0, -# }, -# ), -# ] -# external_orders = [ -# Order.from_dict( -# { -# "id": "1323", -# "side": "buy", -# "symbol": "ETH/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# # Order that is not tracked by the trading bot -# Order.from_dict( -# { -# "id": "133423", -# "side": "buy", -# "symbol": "KSM/EUR", -# "amount": 10, -# "price": 10.0, -# "status": "CLOSED", -# "order_type": "limit", -# "order_side": "buy", -# "created_at": "2023-08-08T14:40:56.626362Z", -# "filled": 10, -# "remaining": 0, -# }, -# ), -# ] -# external_balances = {"EUR": 1000} -# portfolio_configurations = [ -# PortfolioConfiguration( -# market="BITVAVO", -# trading_symbol="EUR" -# ) -# ] -# market_credentials = [ -# MarketCredential( -# market="bitvavo", -# api_key="api_key", -# secret_key="secret_key" -# ) -# ] +class Test(TestBase): + initial_orders = [ + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "BTC/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + Order.from_dict( + { + "id": "14354", + "side": "buy", + "symbol": "DOT/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-09-22T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "ETH/EUR", + "amount": 10, + "price": 10.0, + "status": "OPEN", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 0, + "remaining": 0, + }, + ), + ] + external_orders = [ + Order.from_dict( + { + "id": "1323", + "side": "buy", + "symbol": "ETH/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + # Order that is not tracked by the trading bot + Order.from_dict( + { + "id": "133423", + "side": "buy", + "symbol": "KSM/EUR", + "amount": 10, + "price": 10.0, + "status": "CLOSED", + "order_type": "limit", + "order_side": "buy", + "created_at": "2023-08-08T14:40:56.626362Z", + "filled": 10, + "remaining": 0, + }, + ), + ] + external_balances = {"EUR": 1000} + portfolio_configurations = [ + PortfolioConfiguration( + market="BITVAVO", + trading_symbol="EUR" + ) + ] + market_credentials = [ + MarketCredential( + market="bitvavo", + api_key="api_key", + secret_key="secret_key" + ) + ] -# def test_start_with_new_external_positions(self): -# """ -# Test how the framework handles new external positions on an broker or -# exchange. + def test_start_with_new_external_positions(self): + """ + Test how the framework handles new external positions on an broker or + exchange. -# If the positions where not in the database, the algorithm should -# not include them, because they could be positions from another -# user or from another algorithm. -# """ -# self.assertTrue(self.app.algorithm.has_position("BTC")) -# btc_position = self.app.algorithm.get_position("BTC") -# self.assertEqual(10, btc_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("DOT")) -# dot_position = self.app.algorithm.get_position("DOT") -# self.assertEqual(10, dot_position.get_amount()) -# # Eth position still has open order -# self.assertFalse(self.app.algorithm.has_position("ETH")) -# eth_position = self.app.algorithm.get_position("ETH") -# self.assertEqual(0, eth_position.get_amount()) -# self.assertFalse(self.app.algorithm.has_position("KSM")) -# self.app.run(number_of_iterations=1) -# self.assertTrue(self.app.algorithm.has_position("BTC")) -# btc_position = self.app.algorithm.get_position("BTC") -# self.assertEqual(10, btc_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("DOT")) -# dot_position = self.app.algorithm.get_position("DOT") -# self.assertEqual(10, dot_position.get_amount()) -# self.assertTrue(self.app.algorithm.has_position("ETH")) -# eth_position = self.app.algorithm.get_position("ETH") -# self.assertEqual(10, eth_position.get_amount()) -# self.assertFalse(self.app.algorithm.has_position("KSM")) + If the positions where not in the database, the algorithm should + not include them, because they could be positions from another + user or from another algorithm. + """ + self.assertTrue(self.app.algorithm.has_position("BTC")) + btc_position = self.app.algorithm.get_position("BTC") + self.assertEqual(10, btc_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("DOT")) + dot_position = self.app.algorithm.get_position("DOT") + self.assertEqual(10, dot_position.get_amount()) + # Eth position still has open order + self.assertFalse(self.app.algorithm.has_position("ETH")) + eth_position = self.app.algorithm.get_position("ETH") + self.assertEqual(0, eth_position.get_amount()) + self.assertFalse(self.app.algorithm.has_position("KSM")) + self.app.run(number_of_iterations=1) + self.assertTrue(self.app.algorithm.has_position("BTC")) + btc_position = self.app.algorithm.get_position("BTC") + self.assertEqual(10, btc_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("DOT")) + dot_position = self.app.algorithm.get_position("DOT") + self.assertEqual(10, dot_position.get_amount()) + self.assertTrue(self.app.algorithm.has_position("ETH")) + eth_position = self.app.algorithm.get_position("ETH") + self.assertEqual(10, eth_position.get_amount()) + self.assertFalse(self.app.algorithm.has_position("KSM")) diff --git a/tests/domain/models/test_backtest_report.py b/tests/domain/models/test_backtest_report.py index 2bf01f4d..ac380ecb 100644 --- a/tests/domain/models/test_backtest_report.py +++ b/tests/domain/models/test_backtest_report.py @@ -27,9 +27,9 @@ def test_backtest_reports_evaluation(self): path = os.path.join( self.resource_dir, "backtest_reports_for_testing", - "report_950100_backtest-start-date_2021-12-21:00:00_" - "backtest-end-date_2022-06-20:00:00" - "_created-at_2024-04-25:13:52.json" + "report_950100_backtest-start-date_2021-12-21-00-00_" + "backtest-end-date_2022-06-20-00-00" + "_created-at_2024-04-25-13-52.json" ) report = load_backtest_report(path) self.assertEqual( diff --git a/tests/domain/models/test_order.py b/tests/domain/models/test_order.py index 3ee43511..ed6053d3 100644 --- a/tests/domain/models/test_order.py +++ b/tests/domain/models/test_order.py @@ -34,7 +34,6 @@ def test_from_ccxt_order(self): self.assertEqual(order.get_order_type(), "LIMIT") self.assertEqual(order.get_price(), 10000) self.assertEqual(order.get_amount(), 1) - self.assertEqual(order.get_cost(), 10000) self.assertEqual(order.get_filled(), 1) self.assertEqual(order.get_remaining(), 0) self.assertEqual(order.get_status(), "OPEN") diff --git a/tests/domain/models/test_trade.py b/tests/domain/models/test_trade.py index 31a152a2..802c7bec 100644 --- a/tests/domain/models/test_trade.py +++ b/tests/domain/models/test_trade.py @@ -2,187 +2,202 @@ from datetime import datetime, timedelta from unittest import TestCase -from dateutil.tz import tzutc -from investing_algorithm_framework import CSVOHLCVMarketDataSource, \ - CSVTickerMarketDataSource + +from investing_algorithm_framework import Order from investing_algorithm_framework.domain import Trade class Test(TestCase): def test_trade(self): - trade_opened_at = datetime(2023, 11, 29) - trade = Trade( - buy_order_id=1, + order = Order( + external_id="123", target_symbol="BTC", trading_symbol="EUR", + order_side="BUY", + order_type="LIMIT", + price=10000, amount=1, - open_price=19822.0, - opened_at=trade_opened_at, - closed_price=None, - closed_at=None, + filled=1, + remaining=0, + status="OPEN", + created_at=datetime(2017, 8, 17, 12, 42, 48), ) - self.assertEqual(trade.target_symbol, "BTC") - self.assertEqual(trade.trading_symbol, "EUR") - self.assertEqual(trade.amount, 1) - self.assertEqual(trade.open_price, 19822.0) - self.assertEqual(trade.opened_at, trade_opened_at) - def test_stop_loss_manual_with_dataframe(self): - """ - Test for checking if stoplos function works on trade. The test uses - an ohlcv dataset of BTC/EUR with a 15m timeframe. The start date - of the dataset is 2023-08-07 08:00:00+00:00 and the end - date of the dataset is 2023-12-02 00:00:00+00:00. The trade is - opened at 2023-08-17 12:00:00 with a price of 32589. - """ - current_datetime = datetime(2023, 8, 26, 00, 00, 0, tzinfo=tzutc()) - resource_dir = os.path.abspath( - os.path.join( - os.path.join( - os.path.join( - os.path.join( - os.path.realpath(__file__), - os.pardir - ), - os.pardir - ), - os.pardir - ), - "resources" - ) - ) - csv_ohlcv_market_data_source = CSVOHLCVMarketDataSource( - identifier="BTC", - market="BITVAVO", - symbol="BTC/EUR", - time_frame="15m", - csv_file_path=f"{resource_dir}/" - "market_data_sources_for_testing/" - "OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07" - ":59_2023-12-02:00:00.csv" - ) - csv_ticker_market_data_source = CSVTickerMarketDataSource( - identifier="BTC", - market="BITVAVO", - symbol="BTC/EUR", - csv_file_path=f"{resource_dir}" - "/market_data_sources_for_testing/" - "TICKER_BTC-EUR_BINANCE_2023-08" - "-23:22:00_2023-12-02:00:00.csv" - ) - trade_opened_at = datetime(2023, 8, 24, 22, 0, 0, tzinfo=tzutc()) - open_price = 24167.14 + trade_opened_at = datetime(2023, 11, 29) trade = Trade( - buy_order_id=1, + id=1, + orders=[order], target_symbol="BTC", trading_symbol="EUR", amount=1, - open_price=open_price, + remaining=1, + open_price=10000, opened_at=trade_opened_at, - closed_price=None, closed_at=None, + status="OPEN", + cost=10000, ) - end_date = trade_opened_at + timedelta(days=2) - ohlcv_data = csv_ohlcv_market_data_source \ - .get_data( - market_credential_service=None, - start_date=trade_opened_at, - end_date=end_date - ) - current_price = csv_ticker_market_data_source \ - .get_data( - start_date=end_date, market_credential_service=None - ) - self.assertFalse( - trade.is_manual_stop_loss_trigger( - current_price=current_price["bid"], - ohlcv_df=ohlcv_data, - stop_loss_percentage=2 - ) - ) + self.assertEqual(trade.target_symbol, "BTC") + self.assertEqual(trade.trading_symbol, "EUR") + self.assertEqual(trade.amount, 1) + self.assertEqual(trade.open_price, 10000) - current_datetime = datetime(2023, 11, 21, tzinfo=tzutc()) - ohlcv_data = csv_ohlcv_market_data_source \ - .get_data( - market_credential_service=None, - from_time_stamp=trade_opened_at, - to_time_stamp=current_datetime - ) - current_price = csv_ticker_market_data_source \ - .get_data( - index_datetime=current_datetime, market_credential_service=None - ) - open_price = 35679.63 - trade = Trade( - buy_order_id=1, - target_symbol="BTC", - trading_symbol="EUR", - amount=1, - open_price=open_price, - opened_at=trade_opened_at, - closed_price=None, - closed_at=None, - ) - self.assertFalse( - trade.is_manual_stop_loss_trigger( - current_price=current_price["bid"], - ohlcv_df=ohlcv_data, - stop_loss_percentage=10 - ) - ) - self.assertTrue( - trade.is_manual_stop_loss_trigger( - current_price=current_price["bid"], - ohlcv_df=ohlcv_data, - stop_loss_percentage=2 - ) - ) + # def test_stop_loss_manual_with_dataframe(self): + # """ + # Test for checking if stoplos function works on trade. The test uses + # an ohlcv dataset of BTC/EUR with a 15m timeframe. The start date + # of the dataset is 2023-08-07 08:00:00+00:00 and the end + # date of the dataset is 2023-12-02 00:00:00+00:00. The trade is + # opened at 2023-08-17 12:00:00 with a price of 32589. + # """ + # current_datetime = datetime(2023, 8, 26, 00, 00, 0, tzinfo=tzutc()) + # resource_dir = os.path.abspath( + # os.path.join( + # os.path.join( + # os.path.join( + # os.path.join( + # os.path.realpath(__file__), + # os.pardir + # ), + # os.pardir + # ), + # os.pardir + # ), + # "resources" + # ) + # ) + # csv_ohlcv_market_data_source = CSVOHLCVMarketDataSource( + # identifier="BTC", + # market="BITVAVO", + # symbol="BTC/EUR", + # time_frame="15m", + # csv_file_path=f"{resource_dir}/" + # "market_data_sources_for_testing/" + # "OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07" + # ":59_2023-12-02:00:00.csv" + # ) + # csv_ticker_market_data_source = CSVTickerMarketDataSource( + # identifier="BTC", + # market="BITVAVO", + # symbol="BTC/EUR", + # csv_file_path=f"{resource_dir}" + # "/market_data_sources_for_testing/" + # "TICKER_BTC-EUR_BINANCE_2023-08" + # "-23:22:00_2023-12-02:00:00.csv" + # ) + # trade_opened_at = datetime(2023, 8, 24, 22, 0, 0, tzinfo=tzutc()) + # open_price = 24167.14 + # trade = Trade( + # buy_order_id=1, + # target_symbol="BTC", + # trading_symbol="EUR", + # amount=1, + # open_price=open_price, + # opened_at=trade_opened_at, + # closed_price=None, + # closed_at=None, + # ) + # end_date = trade_opened_at + timedelta(days=2) + # ohlcv_data = csv_ohlcv_market_data_source \ + # .get_data( + # market_credential_service=None, + # start_date=trade_opened_at, + # end_date=end_date + # ) + # current_price = csv_ticker_market_data_source \ + # .get_data( + # start_date=end_date, market_credential_service=None + # ) + # self.assertFalse( + # trade.is_manual_stop_loss_trigger( + # current_price=current_price["bid"], + # ohlcv_df=ohlcv_data, + # stop_loss_percentage=2 + # ) + # ) - def test_stop_loss_manual_trade(self): - trade = Trade( - buy_order_id=1, - target_symbol='BTC', - trading_symbol='USDT', - amount=10, - open_price=100, - opened_at=datetime(2021, 1, 1), - ) - self.assertTrue(trade.is_manual_stop_loss_trigger( - current_price=101, prices=[100, 110, 80], stop_loss_percentage=2 - )) - self.assertFalse(trade.is_manual_stop_loss_trigger( - current_price=101, prices=[100, 110, 80], stop_loss_percentage=20 - )) + # current_datetime = datetime(2023, 11, 21, tzinfo=tzutc()) + # ohlcv_data = csv_ohlcv_market_data_source \ + # .get_data( + # market_credential_service=None, + # from_time_stamp=trade_opened_at, + # to_time_stamp=current_datetime + # ) + # current_price = csv_ticker_market_data_source \ + # .get_data( + # index_datetime=current_datetime, market_credential_service=None + # ) + # open_price = 35679.63 + # trade = Trade( + # buy_order_id=1, + # target_symbol="BTC", + # trading_symbol="EUR", + # amount=1, + # open_price=open_price, + # opened_at=trade_opened_at, + # closed_price=None, + # closed_at=None, + # ) + # self.assertFalse( + # trade.is_manual_stop_loss_trigger( + # current_price=current_price["bid"], + # ohlcv_df=ohlcv_data, + # stop_loss_percentage=10 + # ) + # ) + # self.assertTrue( + # trade.is_manual_stop_loss_trigger( + # current_price=current_price["bid"], + # ohlcv_df=ohlcv_data, + # stop_loss_percentage=2 + # ) + # ) - # Test if the stop loss is triggered when the price - # is lower than the open price - self.assertTrue( - trade.is_manual_stop_loss_trigger( - current_price=80, - prices=[100, 110, 80], - stop_loss_percentage=2 - ) - ) - self.assertFalse( - trade.is_manual_stop_loss_trigger( - current_price=99, - prices=[100, 110, 80], - stop_loss_percentage=2 - ) - ) - self.assertFalse( - trade.is_manual_stop_loss_trigger( - current_price=90, - prices=[100, 110, 80], - stop_loss_percentage=20 - ) - ) - self.assertTrue( - trade.is_manual_stop_loss_trigger( - current_price=80, - prices=[100, 110, 80], - stop_loss_percentage=20 - ) - ) + # def test_stop_loss_manual_trade(self): + # trade = Trade( + # buy_order_id=1, + # target_symbol='BTC', + # trading_symbol='USDT', + # amount=10, + # open_price=100, + # opened_at=datetime(2021, 1, 1), + # ) + # self.assertTrue(trade.is_manual_stop_loss_trigger( + # current_price=101, prices=[100, 110, 80], stop_loss_percentage=2 + # )) + # self.assertFalse(trade.is_manual_stop_loss_trigger( + # current_price=101, prices=[100, 110, 80], stop_loss_percentage=20 + # )) + + # # Test if the stop loss is triggered when the price + # # is lower than the open price + # self.assertTrue( + # trade.is_manual_stop_loss_trigger( + # current_price=80, + # prices=[100, 110, 80], + # stop_loss_percentage=2 + # ) + # ) + # self.assertFalse( + # trade.is_manual_stop_loss_trigger( + # current_price=99, + # prices=[100, 110, 80], + # stop_loss_percentage=2 + # ) + # ) + # self.assertFalse( + # trade.is_manual_stop_loss_trigger( + # current_price=90, + # prices=[100, 110, 80], + # stop_loss_percentage=20 + # ) + # ) + # self.assertTrue( + # trade.is_manual_stop_loss_trigger( + # current_price=80, + # prices=[100, 110, 80], + # stop_loss_percentage=20 + # ) + # ) diff --git a/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_backtest_market_data_source.py b/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_backtest_market_data_source.py index 88ef6ddc..7881e539 100644 --- a/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_backtest_market_data_source.py +++ b/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_backtest_market_data_source.py @@ -44,7 +44,7 @@ def test_file_path(self): and the end date is the backtest end date. """ correct_file_name = \ - "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv" + "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv" data_source = CCXTOHLCVBacktestMarketDataSource( identifier="OHLCV_BTC_EUR_BINANCE_15m", market="BINANCE", @@ -66,7 +66,7 @@ def test_file_path(self): def test_window_size(self): correct_file_name = \ - "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv" + "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv" data_source = CCXTOHLCVBacktestMarketDataSource( identifier="OHLCV_BTC_EUR_BINANCE_15m", market="BINANCE", @@ -87,7 +87,7 @@ def test_window_size(self): def test_right_columns(self): correct_file_name = \ - "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv" + "OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv" csv_file_path = f"{self.resource_dir}/market_data_sources_for_testing"\ f"/{correct_file_name}" data_source = CCXTOHLCVBacktestMarketDataSource( @@ -110,9 +110,10 @@ def test_right_columns(self): self.assertEqual(csv_file_path, data_source._create_file_path()) df = data_source\ .get_data( - start_date=datetime( + date=datetime( year=2023, month=12, day=17, hour=0, minute=0 - ) + ), + config={} ) self.assertEqual( ["Datetime", "Open", "High", "Low", "Close", "Volume"], df.columns diff --git a/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_market_data_source.py b/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_market_data_source.py index 87fa4af5..942158dd 100644 --- a/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_market_data_source.py +++ b/tests/infrastructure/market_data_sources/test_ccxt_ohlcv_market_data_source.py @@ -80,46 +80,34 @@ def test_get_data_with_only_window_size(self, mock): self.assertEqual(data_source.window_size, 200) @mock.patch('investing_algorithm_framework.infrastructure.services.market_service.ccxt_market_service.CCXTMarketService.get_ohlcv') - def test_get_data_with_only_start_date_and_end_date(self, mock): + def test_get_data_with_only_start_date(self, mock): data_source = CCXTOHLCVMarketDataSource( identifier="BTC/EUR", time_frame="15m", market="BITVAVO", symbol="BTC/EUR", + window_size=200 ) mock.return_value = {'key': 'value'} data = data_source.get_data( - start_date=datetime(2021, 1, 1), - end_date=datetime(2021, 1, 2) + start_date=datetime(2021, 1, 1) ) self.assertIsNotNone(data) @mock.patch('investing_algorithm_framework.infrastructure.services.market_service.ccxt_market_service.CCXTMarketService.get_ohlcv') - def test_get_data_with_only_start_date_and_window_size(self, mock): + def test_get_data_with_only_date_and_end_date(self, mock): data_source = CCXTOHLCVMarketDataSource( identifier="BTC/EUR", time_frame="15m", market="BITVAVO", symbol="BTC/EUR", - ) - mock.return_value = {'key': 'value'} - data = data_source.get_data( - start_date=datetime(2021, 1, 1), window_size=200 ) - self.assertIsNotNone(data) - - @mock.patch('investing_algorithm_framework.infrastructure.services.market_service.ccxt_market_service.CCXTMarketService.get_ohlcv') - def test_get_data_with_only_end_date_and_window_size(self, mock): - data_source = CCXTOHLCVMarketDataSource( - identifier="BTC/EUR", - time_frame="15m", - market="BITVAVO", - symbol="BTC/EUR", - ) mock.return_value = {'key': 'value'} + start_date = datetime(2021, 1, 1) + end_date = datetime(2021, 1, 2) data = data_source.get_data( - start_date=datetime(2021, 1, 1), - window_size=200 + start_date=start_date, + end_date=end_date ) self.assertIsNotNone(data) diff --git a/tests/infrastructure/market_data_sources/test_csv_ohlcv_market_data_source.py b/tests/infrastructure/market_data_sources/test_csv_ohlcv_market_data_source.py index b114f1b0..16c1542a 100644 --- a/tests/infrastructure/market_data_sources/test_csv_ohlcv_market_data_source.py +++ b/tests/infrastructure/market_data_sources/test_csv_ohlcv_market_data_source.py @@ -7,7 +7,7 @@ from polars import DataFrame from investing_algorithm_framework.domain import OperationalException, \ - TimeFrame, DATETIME_FORMAT + DATETIME_FORMAT from investing_algorithm_framework.infrastructure import \ CSVOHLCVMarketDataSource @@ -36,38 +36,39 @@ def setUp(self) -> None: def test_right_columns(self): file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" data_source = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/market_data_sources/" f"{file_name}", + window_size=10, ) - df = data_source.get_data() + date = datetime(2023, 8, 7, 8, 0, tzinfo=tzutc()) + df = data_source.get_data(start_date=date) self.assertEqual( ["Datetime", "Open", "High", "Low", "Close", "Volume"], df.columns ) def test_throw_exception_when_missing_column_names_columns(self): file_name = "OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-" \ - "08-07:07:59_2023-12-02:00:00.csv" + "08-07-07-59_2023-12-02-00-00.csv" with self.assertRaises(OperationalException): CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources_for_testing/" f"{file_name}", - window_size=10 + window_size=10, ) def test_start_date(self): start_date = datetime(2023, 8, 7, 8, 0, tzinfo=tzutc()) file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" csv_ohlcv_market_data_source = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", window_size=10, - time_frame=TimeFrame.TWO_HOUR, ) self.assertEqual( start_date, @@ -80,15 +81,16 @@ def test_start_date_with_window_size(self): year=2023, month=8, day=7, hour=10, minute=0, tzinfo=tzutc() ) file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" csv_ohlcv_market_data_source = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", window_size=12, - time_frame=TimeFrame.TWO_HOUR, ) - data = csv_ohlcv_market_data_source.get_data(start_date=start_date) + data = csv_ohlcv_market_data_source.get_data( + start_date=start_date + ) self.assertEqual(12, len(data)) first_date = parser.parse(data["Datetime"][0]) self.assertEqual( @@ -99,7 +101,7 @@ def test_start_date_with_window_size(self): def test_end_date(self): end_date = datetime(2023, 12, 2, 0, 0, tzinfo=tzutc()) file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" csv_ohlcv_market_data_source = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" @@ -113,33 +115,30 @@ def test_end_date(self): def test_empty(self): file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" data_source = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", window_size=10, - time_frame="2h", ) - start_date = datetime(2023, 8, 7, 8, 0, tzinfo=tzutc()) - end_date = datetime(2023, 12, 2, 0, 0, tzinfo=tzutc()) - self.assertFalse(data_source.empty(start_date, end_date)) + start_date = datetime(2023, 12, 2, 0, 0, tzinfo=tzutc()) + self.assertFalse(data_source.empty(start_date)) def test_get_data(self): file_name = \ - "OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21:14:00_2024-06-07:10:00.csv" + "OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21-14-00_2024-06-07-10-00.csv" datasource = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources_for_testing/" f"{file_name}", window_size=200, - time_frame="2h", ) number_of_runs = 0 backtest_index_date = datasource._start_date_data_source - while not datasource.empty(start_date=backtest_index_date): - data = datasource.get_data(start_date=backtest_index_date) + while not datasource.empty(end_date=backtest_index_date): + data = datasource.get_data(end_date=backtest_index_date) backtest_index_date = \ backtest_index_date + timedelta(hours=2 * 200) self.assertTrue(len(data) > 0) @@ -150,50 +149,35 @@ def test_get_data(self): def test_get_identifier(self): file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" datasource = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", identifier="test", window_size=10, - time_frame="2h", ) self.assertEqual("test", datasource.get_identifier()) def test_get_market(self): file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" datasource = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", market="test", - time_frame="2h", ) self.assertEqual("test", datasource.get_market()) def test_get_symbol(self): file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" + "_2h_2023-08-07-07-59_2023-12-02-00-00.csv" datasource = CSVOHLCVMarketDataSource( csv_file_path=f"{self.resource_dir}/" "market_data_sources/" f"{file_name}", symbol="BTC/EUR", window_size=10, - time_frame="2h", ) self.assertEqual("BTC/EUR", datasource.get_symbol()) - - def test_get_timeframe(self): - file_name = "OHLCV_BTC-EUR_BINANCE" \ - "_2h_2023-08-07:07:59_2023-12-02:00:00.csv" - datasource = CSVOHLCVMarketDataSource( - csv_file_path=f"{self.resource_dir}/" - "market_data_sources/" - f"{file_name}", - time_frame="2h", - window_size=10, - ) - self.assertTrue(TimeFrame.TWO_HOUR.equals(datasource.get_time_frame())) diff --git a/tests/infrastructure/repositories/test_trade_repository.py b/tests/infrastructure/repositories/test_trade_repository.py new file mode 100644 index 00000000..03a2ec5f --- /dev/null +++ b/tests/infrastructure/repositories/test_trade_repository.py @@ -0,0 +1,49 @@ +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential, TradeStatus +from tests.resources import TestBase + + +class Test(TestBase): + market_credentials = [ + MarketCredential( + market="BINANCE", + api_key="api_key", + secret_key="secret_key" + ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="BINANCE", + trading_symbol="EUR" + ) + ] + external_balances = { + "EUR": 1000, + } + + def test_get_all_with_status(self): + order_service = self.app.container.order_service() + trade_service = self.app.container.trade_service() + order = order_service.create( + { + "portfolio_id": 1, + "target_symbol": "BTC", + "amount": 1, + "trading_symbol": "EUR", + "price": 10, + "order_side": "BUY", + "order_type": "LIMIT", + "status": "OPEN", + } + ) + trades = trade_service.get_all({"status": TradeStatus.CREATED.value}) + self.assertEqual(1, len(trades)) + trades = trade_service.get_all({"status": TradeStatus.OPEN.value}) + self.assertEqual(0, len(trades)) + order_service.update(order.id, {"filled": 10}) + trades = trade_service.get_all({"status": TradeStatus.CREATED.value}) + self.assertEqual(0, len(trades)) + trades = trade_service.get_all({"status": TradeStatus.OPEN.value}) + self.assertEqual(1, len(trades)) + trades = trade_service.get_all({"status": TradeStatus.CLOSED.value}) + self.assertEqual(0, len(trades)) diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py index 521dab2d..9d89c108 100644 --- a/tests/resources/__init__.py +++ b/tests/resources/__init__.py @@ -1,4 +1,5 @@ -from .stubs import MarketServiceStub, RandomPriceMarketDataSourceServiceStub +from .stubs import MarketServiceStub, RandomPriceMarketDataSourceServiceStub,\ + MarketDataSourceServiceStub from .test_base import TestBase, FlaskTestBase from .utils import random_string @@ -7,5 +8,6 @@ "TestBase", "MarketServiceStub", "FlaskTestBase", - "RandomPriceMarketDataSourceServiceStub" + "RandomPriceMarketDataSourceServiceStub", + "MarketDataSourceServiceStub" ] diff --git a/tests/resources/backtest_reports_for_testing/report_950100_backtest-start-date_2021-12-21:00:00_backtest-end-date_2022-06-20:00:00_created-at_2024-04-25:13:52.json b/tests/resources/backtest_reports_for_testing/report_950100_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json similarity index 100% rename from tests/resources/backtest_reports_for_testing/report_950100_backtest-start-date_2021-12-21:00:00_backtest-end-date_2022-06-20:00:00_created-at_2024-04-25:13:52.json rename to tests/resources/backtest_reports_for_testing/report_950100_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json diff --git a/tests/resources/backtest_reports_for_testing/report_test_backtest-start-date_2021-12-21:00:00_backtest-end-date_2022-06-20:00:00_created-at_2024-04-25:13:52.json b/tests/resources/backtest_reports_for_testing/report_test_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json similarity index 100% rename from tests/resources/backtest_reports_for_testing/report_test_backtest-start-date_2021-12-21:00:00_backtest-end-date_2022-06-20:00:00_created-at_2024-04-25:13:52.json rename to tests/resources/backtest_reports_for_testing/report_test_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json diff --git a/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv b/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv rename to tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv diff --git a/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:22:00_2023-12-25:00:00.csv b/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:22:00_2023-12-25:00:00.csv rename to tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv diff --git a/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv b/tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv rename to tests/resources/market_data_sources/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv b/tests/resources/market_data_sources/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv rename to tests/resources/market_data_sources/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources/TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv b/tests/resources/market_data_sources/TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv rename to tests/resources/market_data_sources/TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources/TICKER_DOT-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv b/tests/resources/market_data_sources/TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources/TICKER_DOT-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv rename to tests/resources/market_data_sources/TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:21:45_2023-12-25:00:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:22:00_2023-12-25:00:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14:22:00_2023-12-25:00:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-08-07:07:59_2023-12-02:00:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-08-07-07-59_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-08-07:07:59_2023-12-02:00:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-08-07-07-59_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21:14:00_2024-06-07:10:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21-14-00_2024-06-07-10-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21:14:00_2024-06-07:10:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21-14-00_2024-06-07-10-00.csv diff --git a/tests/resources/market_data_sources_for_testing/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv b/tests/resources/market_data_sources_for_testing/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07:07:59_2023-12-02:00:00.csv rename to tests/resources/market_data_sources_for_testing/OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv b/tests/resources/market_data_sources_for_testing/TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv rename to tests/resources/market_data_sources_for_testing/TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv diff --git a/tests/resources/market_data_sources_for_testing/TICKER_DOT-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv b/tests/resources/market_data_sources_for_testing/TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv similarity index 100% rename from tests/resources/market_data_sources_for_testing/TICKER_DOT-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv rename to tests/resources/market_data_sources_for_testing/TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv diff --git a/tests/resources/stubs/__init__.py b/tests/resources/stubs/__init__.py index bdcff571..f57b78d4 100644 --- a/tests/resources/stubs/__init__.py +++ b/tests/resources/stubs/__init__.py @@ -1,8 +1,9 @@ from .market_data_source_service_stub import \ - RandomPriceMarketDataSourceServiceStub + RandomPriceMarketDataSourceServiceStub, MarketDataSourceServiceStub from .market_service_stub import MarketServiceStub __all__ = [ "MarketServiceStub", - "RandomPriceMarketDataSourceServiceStub" + "RandomPriceMarketDataSourceServiceStub", + "MarketDataSourceServiceStub" ] diff --git a/tests/resources/stubs/market_data_source_service_stub.py b/tests/resources/stubs/market_data_source_service_stub.py index 1b9979e6..9538dce8 100644 --- a/tests/resources/stubs/market_data_source_service_stub.py +++ b/tests/resources/stubs/market_data_source_service_stub.py @@ -15,3 +15,21 @@ def get_ticker(self, symbol, market=None): "volume": randint(1, 100), "timestamp": datetime.utcnow() } + +class MarketDataSourceServiceStub(MarketDataSourceService): + + def __init__(self): + pass + + def initialize_market_data_sources(self): + pass + + def get_ticker(self, symbol, market=None): + return { + "symbol": symbol, + "ask": randint(1, 100), + "bid": randint(1, 100), + "last": randint(1, 100), + "volume": randint(1, 100), + "timestamp": datetime.utcnow() + } diff --git a/tests/resources/test_base.py b/tests/resources/test_base.py index f899c0cc..b8b61335 100644 --- a/tests/resources/test_base.py +++ b/tests/resources/test_base.py @@ -162,9 +162,7 @@ def create_app(self): self.market_service.orders = self.external_orders self.iaf_app.container.market_service.override(self.market_service) - if self.initialize: - - if len(self.portfolio_configurations) > 0: + if len(self.portfolio_configurations) > 0: for portfolio_configuration in self.portfolio_configurations: self.iaf_app.add_portfolio_configuration( portfolio_configuration @@ -177,7 +175,9 @@ def create_app(self): for market_credential in self.market_credentials: self.iaf_app.add_market_credential(market_credential) - self.iaf_app.initialize() + if self.initialize: + self.iaf_app.initialize_config() + self.iaf_app.initialize() if self.initial_orders is not None: for order in self.initial_orders: diff --git a/tests/services/test_backtest_service.py b/tests/services/test_backtest_service.py index 75de1aef..1dbb83d2 100644 --- a/tests/services/test_backtest_service.py +++ b/tests/services/test_backtest_service.py @@ -60,27 +60,14 @@ def test_is_backtest_report(self): ) self.assertFalse(backtest_service._is_backtest_report(path)) - def test_get_report(self): - backtest_service = self.app.container.backtest_service() - date_range = BacktestDateRange( - start_date="2021-12-21 00:00", - end_date="2022-06-20 00:00" - ) - report = backtest_service.get_report( - algorithm_name="test", - backtest_date_range=date_range, directory=self.backtest_report_dir - ) - self.assertIsNotNone(report) - - def test_get_report(self): - backtest_service = self.app.container.backtest_service() - date_range = BacktestDateRange( - start_date="2021-12-21 00:00", - end_date="2022-06-20 00:00" - ) - report = backtest_service.get_report( - algorithm_name="950100", - backtest_date_range=date_range, - directory=self.backtest_report_dir - ) - self.assertIsNotNone(report) + # def test_get_report(self): + # backtest_service = self.app.container.backtest_service() + # date_range = BacktestDateRange( + # start_date="2021-12-21 00:00", + # end_date="2022-06-20 00:00" + # ) + # report = backtest_service.get_report( + # algorithm_name="test", + # backtest_date_range=date_range, directory=self.backtest_report_dir + # ) + # self.assertIsNotNone(report) diff --git a/tests/services/test_market_data_source_service.py b/tests/services/test_market_data_source_service.py index 52cfca95..cfb48f79 100644 --- a/tests/services/test_market_data_source_service.py +++ b/tests/services/test_market_data_source_service.py @@ -34,7 +34,7 @@ def setUp(self) -> None: csv_file_path=os.path.join( config[RESOURCE_DIRECTORY], "market_data_sources", - "TICKER_BTC-EUR_BINANCE_2023-08-23:22:00_2023-12-02:00:00.csv" + "TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv" ) )) diff --git a/tests/services/test_order_backtest_service.py b/tests/services/test_order_backtest_service.py index c8bc5d53..1462e193 100644 --- a/tests/services/test_order_backtest_service.py +++ b/tests/services/test_order_backtest_service.py @@ -4,10 +4,12 @@ import polars as pl from investing_algorithm_framework import PortfolioConfiguration, \ - MarketCredential, \ - BACKTESTING_INDEX_DATETIME + MarketCredential, BACKTESTING_INDEX_DATETIME from investing_algorithm_framework.services import \ BacktestMarketDataSourceService, OrderBacktestService +from investing_algorithm_framework.domain import ENVIRONMENT, \ + DATABASE_NAME, DATABASE_DIRECTORY_NAME, Environment, \ + BACKTESTING_START_DATE, BACKTESTING_END_DATE, BACKTESTING_INITIAL_AMOUNT from tests.resources import TestBase @@ -61,6 +63,7 @@ def setUp(self) -> None: self.app.container.order_service.override( OrderBacktestService( + trade_service=self.app.container.trade_service(), order_repository=self.app.container.order_repository(), position_repository=self.app.container.position_repository(), portfolio_repository=self.app.container.portfolio_repository(), @@ -73,6 +76,17 @@ def setUp(self) -> None: market_data_source_service=backtest_market_data_source_service ) ) + self.app.set_config_with_dict( + { + ENVIRONMENT: Environment.BACKTEST.value, + DATABASE_NAME: "backtest-database.sqlite3", + DATABASE_DIRECTORY_NAME: "backtest_databases", + BACKTESTING_START_DATE: datetime(2023, 8, 8), + BACKTESTING_END_DATE: datetime(2023, 8, 10), + BACKTESTING_INITIAL_AMOUNT: 1000 + } + ) + self.app.initialize_config() self.app.initialize() def test_create_limit_order(self): @@ -104,7 +118,7 @@ def test_create_limit_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) def test_update_order(self): order_service = self.app.container.order_service() @@ -170,7 +184,7 @@ def test_create_limit_buy_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) def test_create_limit_sell_order(self): order_service = self.app.container.order_service() @@ -200,7 +214,7 @@ def test_create_limit_sell_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) order_service.update( order.id, @@ -243,118 +257,6 @@ def test_update_buy_order_with_successful_order_filled(self): def test_update_sell_order_with_successful_order(self): pass - def test_update_closing_partial_buy_orders(self): - order_service = self.app.container.order_service() - configuration_service = self.app.container.configuration_service() - configuration_service.add_value( - BACKTESTING_INDEX_DATETIME, - datetime.utcnow() - ) - buy_order_one = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 5, - "order_side": "BUY", - "price": 0.24262, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - order_service.update( - buy_order_one.id, - { - "status": "CLOSED", - "filled": 5, - "remaining": Decimal('0'), - } - ) - buy_order_two = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 5, - "order_side": "BUY", - "price": 0.24262, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - order_service.update( - buy_order_two.id, - { - "status": "CLOSED", - "filled": 5, - "remaining": Decimal('0'), - } - ) - sell_order_one = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 2.5, - "order_side": "SELL", - "price": 0.24262, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - order_service.update( - sell_order_one.id, - { - "status": "CLOSED", - "filled": 2.5, - "remaining": Decimal('0'), - } - ) - buy_order_one = order_service.get(buy_order_one.id) - buy_order_two = order_service.get(buy_order_two.id) - self.assertEqual( - 2.5, - buy_order_one.get_filled() - - buy_order_one.get_trade_closed_amount() - ) - self.assertEqual( - 5, - buy_order_two.get_filled() - - buy_order_two.get_trade_closed_amount() - ) - sell_order_two = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 5, - "order_side": "SELL", - "price": 0.24262, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - order_service.update( - sell_order_two.id, - { - "status": "CLOSED", - "filled": 5, - "remaining": Decimal('0'), - } - ) - buy_order_one = order_service.get(buy_order_one.id) - buy_order_two = order_service.get(buy_order_two.id) - self.assertEqual( - 0, - buy_order_one.get_filled() - - buy_order_one.get_trade_closed_amount() - ) - self.assertEqual( - 2.5, - buy_order_two.get_filled() - - buy_order_two.get_trade_closed_amount() - ) - def test_update_sell_order_with_successful_order_filled(self): pass @@ -370,132 +272,6 @@ def test_update_buy_order_with_cancelled_order(self): def test_update_sell_order_with_cancelled_order(self): pass - def test_trade_closing_winning_trade(self): - order_service = self.app.container.order_service() - configuration_service = self.app.container.configuration_service() - configuration_service.add_value( - BACKTESTING_INDEX_DATETIME, - datetime.utcnow() - ) - buy_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "BUY", - "price": 0.2, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - updated_buy_order = order_service.update( - buy_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(updated_buy_order.amount, 1000) - self.assertEqual(updated_buy_order.filled, 1000) - self.assertEqual(updated_buy_order.remaining, 0) - - # Create a sell order with a higher price - sell_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "SELL", - "price": 0.3, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - self.assertEqual(0.3, sell_order.get_price()) - updated_sell_order = order_service.update( - sell_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(0.3, updated_sell_order.get_price()) - self.assertEqual(updated_sell_order.amount, 1000) - self.assertEqual(updated_sell_order.filled, 1000) - self.assertEqual(updated_sell_order.remaining, 0) - buy_order = order_service.get(buy_order.id) - self.assertEqual(buy_order.status, "CLOSED") - self.assertIsNotNone(buy_order.get_trade_closed_at()) - self.assertIsNotNone(buy_order.get_trade_closed_price()) - self.assertNotEqual(0, buy_order.get_net_gain()) - - def test_trade_closing_losing_trade(self): - order_service = self.app.container.order_service() - configuration_service = self.app.container.configuration_service() - configuration_service.add_value( - BACKTESTING_INDEX_DATETIME, - datetime.utcnow() - ) - buy_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "BUY", - "price": 0.2, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - updated_buy_order = order_service.update( - buy_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(updated_buy_order.amount, 1000) - self.assertEqual(updated_buy_order.filled, 1000) - self.assertEqual(updated_buy_order.remaining, 0) - - # Create a sell order with a higher price - sell_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "SELL", - "price": 0.1, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - self.assertEqual(0.1, sell_order.get_price()) - updated_sell_order = order_service.update( - sell_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(0.1, updated_sell_order.get_price()) - self.assertEqual(updated_sell_order.amount, 1000) - self.assertEqual(updated_sell_order.filled, 1000) - self.assertEqual(updated_sell_order.remaining, 0) - buy_order = order_service.get(buy_order.id) - self.assertEqual(buy_order.status, "CLOSED") - self.assertIsNotNone(buy_order.get_trade_closed_at()) - self.assertIsNotNone(buy_order.get_trade_closed_price()) - self.assertEqual(-100, buy_order.get_net_gain()) - def test_has_executed_buy_order(self): order_service = self.app.container.order_service() configuration_service = self.app.container.configuration_service() @@ -526,7 +302,7 @@ def test_has_executed_buy_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) # Check with ohlcv data with a single row that matches the price # of the buy order @@ -657,7 +433,7 @@ def test_has_executed_sell_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) # Update the buy order to closed order_service.update( diff --git a/tests/services/test_order_service.py b/tests/services/test_order_service.py index e275967a..f5c5248d 100644 --- a/tests/services/test_order_service.py +++ b/tests/services/test_order_service.py @@ -46,7 +46,7 @@ def test_create_limit_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) def test_update_order(self): order_service = self.app.container.order_service() @@ -101,7 +101,7 @@ def test_create_limit_buy_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) def test_create_limit_sell_order(self): order_service = self.app.container.order_service() @@ -126,7 +126,7 @@ def test_create_limit_sell_order(self): self.assertEqual("EUR", order.get_trading_symbol()) self.assertEqual("BUY", order.get_order_side()) self.assertEqual("LIMIT", order.get_order_type()) - self.assertEqual("CREATED", order.get_status()) + self.assertEqual("OPEN", order.get_status()) order_service.update( order.id, @@ -233,16 +233,8 @@ def test_update_sell_order_closing_partial_buy_orders(self): ) buy_order_one = order_service.get(buy_order_one.id) buy_order_two = order_service.get(buy_order_two.id) - self.assertEqual( - 2.5, - buy_order_one.get_filled() - - buy_order_one.get_trade_closed_amount() - ) - self.assertEqual( - 5, - buy_order_two.get_filled() - - buy_order_two.get_trade_closed_amount() - ) + self.assertEqual(5, buy_order_one.get_filled()) + self.assertEqual(5, buy_order_two.get_filled()) sell_order_two = order_service.create( { "target_symbol": "ADA", @@ -265,16 +257,8 @@ def test_update_sell_order_closing_partial_buy_orders(self): ) buy_order_one = order_service.get(buy_order_one.id) buy_order_two = order_service.get(buy_order_two.id) - self.assertEqual( - 0, - buy_order_one.get_filled() - - buy_order_one.get_trade_closed_amount() - ) - self.assertEqual( - 2.5, - buy_order_two.get_filled() - - buy_order_two.get_trade_closed_amount() - ) + self.assertEqual(5, buy_order_one.get_filled()) + self.assertEqual(5, buy_order_two.get_filled()) def test_update_sell_order_with_successful_order_filled(self): pass @@ -290,119 +274,3 @@ def test_update_buy_order_with_cancelled_order(self): def test_update_sell_order_with_cancelled_order(self): pass - - def test_trade_closing_winning_trade(self): - order_service = self.app.container.order_service() - buy_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "BUY", - "price": 0.2, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - updated_buy_order = order_service.update( - buy_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(updated_buy_order.amount, 1000) - self.assertEqual(updated_buy_order.filled, 1000) - self.assertEqual(updated_buy_order.remaining, 0) - - # Create a sell order with a higher price - sell_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "SELL", - "price": 0.3, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - self.assertEqual(0.3, sell_order.get_price()) - updated_sell_order = order_service.update( - sell_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(0.3, updated_sell_order.get_price()) - self.assertEqual(updated_sell_order.amount, 1000) - self.assertEqual(updated_sell_order.filled, 1000) - self.assertEqual(updated_sell_order.remaining, 0) - buy_order = order_service.get(buy_order.id) - self.assertEqual(buy_order.status, "CLOSED") - self.assertIsNotNone(buy_order.get_trade_closed_at()) - self.assertIsNotNone(buy_order.get_trade_closed_price()) - self.assertNotEqual(0, buy_order.get_net_gain()) - - def test_trade_closing_losing_trade(self): - order_service = self.app.container.order_service() - buy_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "BUY", - "price": 0.2, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - updated_buy_order = order_service.update( - buy_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(updated_buy_order.amount, 1000) - self.assertEqual(updated_buy_order.filled, 1000) - self.assertEqual(updated_buy_order.remaining, 0) - - # Create a sell order with a higher price - sell_order = order_service.create( - { - "target_symbol": "ADA", - "trading_symbol": "EUR", - "amount": 1000, - "order_side": "SELL", - "price": 0.1, - "order_type": "LIMIT", - "portfolio_id": 1, - "status": "CREATED", - } - ) - self.assertEqual(0.1, sell_order.get_price()) - updated_sell_order = order_service.update( - sell_order.id, - { - "status": "CLOSED", - "filled": 1000, - "remaining": 0, - } - ) - self.assertEqual(0.1, updated_sell_order.get_price()) - self.assertEqual(updated_sell_order.amount, 1000) - self.assertEqual(updated_sell_order.filled, 1000) - self.assertEqual(updated_sell_order.remaining, 0) - buy_order = order_service.get(buy_order.id) - self.assertEqual(buy_order.status, "CLOSED") - self.assertIsNotNone(buy_order.get_trade_closed_at()) - self.assertIsNotNone(buy_order.get_trade_closed_price()) - self.assertEqual(-100, buy_order.get_net_gain()) diff --git a/tests/services/test_trade_service.py b/tests/services/test_trade_service.py new file mode 100644 index 00000000..8cf1920a --- /dev/null +++ b/tests/services/test_trade_service.py @@ -0,0 +1,660 @@ +from investing_algorithm_framework import PortfolioConfiguration, \ + MarketCredential, OrderStatus, TradeStatus +from tests.resources import TestBase + + +class TestTradeService(TestBase): + market_credentials = [ + MarketCredential( + market="binance", + api_key="api_key", + secret_key="secret_key", + ) + ] + portfolio_configurations = [ + PortfolioConfiguration( + market="binance", + trading_symbol="EUR" + ) + ] + external_balances = { + "EUR": 1000 + } + + def test_create_trade_from_buy_order(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 2004, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "CREATED", + } + ) + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(2004, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNotNone(trade.opened_at) + self.assertIsNone(trade.closed_at) + self.assertEqual(2004, trade.remaining) + + def test_create_trade_from_buy_order_with_rejected_buy_order(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 2004, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "REJECTED", + } + ) + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertIsNone(trade) + + def test_create_trade_from_buy_order_with_canceled_buy_order(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 2004, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "CANCELED", + } + ) + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertIsNone(trade) + + def test_create_trade_from_buy_order_with_expired_buy_order(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 2004, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "EXPIRED", + } + ) + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertIsNone(trade) + + def test_update_trade(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 0, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "CREATED", + } + ) + order_id = buy_order.id + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(0, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNotNone(trade.opened_at) + self.assertIsNone(trade.closed_at) + self.assertEqual(0, trade.remaining) + buy_order = order_repository.get(order_id) + buy_order = order_repository.update( + buy_order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 2004, + } + ) + trade = trade_service.update_trade_with_buy_order(2004, buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(2004, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNone(trade.closed_at) + self.assertEqual(2004, trade.remaining) + + def test_update_trade_with_existing_buy_order(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 1000, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "CREATED", + } + ) + order_id = buy_order.id + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(1000, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNotNone(trade.opened_at) + self.assertIsNone(trade.closed_at) + self.assertEqual(1000, trade.remaining) + buy_order = order_repository.get(order_id) + buy_order = order_repository.update( + buy_order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 2004, + } + ) + trade = trade_service.update_trade_with_buy_order(1004, buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(2004, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNone(trade.closed_at) + self.assertEqual(2004, trade.remaining) + + def test_update_trade_with_existing_buy_order_and_partily_closed(self): + order_repository = self.app.container.order_repository() + buy_order = order_repository.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 1000, + "order_side": "BUY", + "price": 0.24262, + "order_type": "LIMIT", + "status": "CREATED", + } + ) + order_id = buy_order.id + trade_service = self.app.container.trade_service() + trade = trade_service.create_trade_from_buy_order(buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(1000, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNotNone(trade.opened_at) + self.assertIsNone(trade.closed_at) + self.assertEqual(1000, trade.remaining) + buy_order = order_repository.get(order_id) + buy_order = order_repository.update( + buy_order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 2004, + } + ) + trade = trade_service.update_trade_with_buy_order(1004, buy_order) + self.assertEqual("ADA", trade.target_symbol) + self.assertEqual("EUR", trade.trading_symbol) + self.assertEqual(2004, trade.amount) + self.assertEqual(0.24262, trade.open_price) + self.assertIsNone(trade.closed_at) + self.assertEqual(2004, trade.remaining) + + def test_close_trades(self): + portfolio = self.app.algorithm.get_portfolio() + order_service = self.app.container.order_service() + order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 0, + "order_side": "BUY", + "price": 0.2, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + trade_service = self.app.container.trade_service() + + order = order_service.find({ + "target_symbol": "ADA", + "trading_symbol": "EUR", + "portfolio_id": portfolio.id + }) + + # Update the buy order to closed + order_service = self.app.container.order_service() + order_service.update( + order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 2004, + } + ) + + # Check that the trade was updated + trade = trade_service.find( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + self.assertEqual(2004, trade.amount) + self.assertEqual(2004, trade.remaining) + self.assertEqual(TradeStatus.OPEN.value, trade.status) + self.assertEqual(0.2, trade.open_price) + self.assertEqual(1, len(trade.orders)) + + order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 0, + "order_side": "SELL", + "price": 0.3, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + trade_service = self.app.container.trade_service() + # Check that the trade was updated + trade = trade_service.find( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + self.assertEqual(2004, trade.amount) + self.assertEqual(2004, trade.remaining) + self.assertEqual(TradeStatus.OPEN.value, trade.status) + self.assertEqual(0.2, trade.open_price) + # Amount of orders should be 1, because the sell order has not + # been filled + self.assertEqual(1, len(trade.orders)) + + # Update the sell order to closed + order = order_service.find({ + "target_symbol": "ADA", + "trading_symbol": "EUR", + "order_side": "SELL", + "portfolio_id": portfolio.id + }) + order_service.update( + order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 2004, + } + ) + + trade_service = self.app.container.trade_service() + # Check that the trade was updated + trade = trade_service.find( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + self.assertEqual(2004, trade.amount) + self.assertEqual(0, trade.remaining) + self.assertEqual(TradeStatus.CLOSED.value, trade.status) + self.assertEqual(0.2, trade.open_price) + self.assertAlmostEqual(2004 * 0.3 - 2004 * 0.2, trade.net_gain) + self.assertEqual(2, len(trade.orders)) + + def test_close_trades_with_no_open_trades(self): + portfolio = self.app.algorithm.get_portfolio() + order_service = self.app.container.order_service() + order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 0, + "order_side": "BUY", + "price": 0.2, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + + with self.assertRaises(Exception) as context: + order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2004, + "filled": 0, + "order_side": "SELL", + "price": 0.3, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + + self.assertIn( + "Order amount 2004 is larger then amount of open position 0.0", + str(context.exception) + ) + + def test_close_trades_with_multiple_trades(self): + portfolio = self.app.algorithm.get_portfolio() + order_service = self.app.container.order_service() + buy_order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2000, + "filled": 0, + "order_side": "BUY", + "price": 0.2, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + order_one_id = buy_order.id + + buy_order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 1000, + "filled": 0, + "order_side": "BUY", + "price": 0.25, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + order_two_id = buy_order.id + + orders = order_service.get_all({ + "target_symbol": "ADA", + "trading_symbol": "EUR", + "portfolio_id": portfolio.id + }) + + # Update the buy order to closed + order_service = self.app.container.order_service() + + for order in orders: + order_service.update( + order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": order.amount, + } + ) + + # Check that the trade was updated + trade_service = self.app.container.trade_service() + self.assertEqual(2, len(trade_service.get_all())) + trades = trade_service.get_all( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + + for t in trades: + self.assertNotEqual(0, t.amount) + self.assertEqual(t.amount, t.remaining) + self.assertEqual(TradeStatus.OPEN.value, t.status) + self.assertEqual(1, len(t.orders)) + + order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 3000, + "filled": 0, + "order_side": "SELL", + "price": 0.3, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + trade_service = self.app.container.trade_service() + self.assertEqual(2, len(trade_service.get_all())) + trades = trade_service.get_all( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + + for t in trades: + self.assertNotEqual(0, t.amount) + self.assertEqual(t.amount, t.remaining) + self.assertEqual(TradeStatus.OPEN.value, t.status) + self.assertEqual(1, len(t.orders)) + + + # Update the sell order to closed + order = order_service.find({ + "target_symbol": "ADA", + "trading_symbol": "EUR", + "order_side": "SELL", + "portfolio_id": portfolio.id + }) + order_service.update( + order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": order.amount, + } + ) + + trade_service = self.app.container.trade_service() + self.assertEqual(2, len(trade_service.get_all())) + trades = trade_service.get_all( + {"target_symbol": "ADA", "trading_symbol": "EUR"} + ) + + for t in trades: + self.assertNotEqual(0, t.amount) + self.assertNotEqual(t.amount, t.remaining) + self.assertEqual(TradeStatus.CLOSED.value, t.status) + self.assertEqual(2, len(t.orders)) + self.assertEqual(0, t.remaining) + + trade = trade_service.find({"order_id": order_one_id}) + self.assertEqual(200, trade.net_gain) + + trade = trade_service.find({"order_id": order_two_id}) + self.assertEqual(50, trade.net_gain) + + def test_close_trades_with_partailly_filled_buy_order(self): + portfolio = self.app.algorithm.get_portfolio() + order_service = self.app.container.order_service() + order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 2000, + "filled": 0, + "order_side": "BUY", + "price": 0.2, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + order_id = order.id + order_service.update( + order.id, + { + "status": OrderStatus.OPEN.value, + "filled": order.amount / 2, + } + ) + + trade_service = self.app.container.trade_service() + trade = trade_service.find( + {"order_id": order_id} + ) + self.assertEqual(1000, trade.amount) + self.assertEqual(1000, trade.remaining) + self.assertEqual(TradeStatus.OPEN.value, trade.status) + self.assertEqual(0.2, trade.open_price) + self.assertEqual(1, len(trade.orders)) + order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 1000, + "filled": 0, + "order_side": "SELL", + "price": 0.3, + "order_type": "LIMIT", + "status": "CREATED", + "portfolio_id": portfolio.id, + } + ) + + order_service.update( + order.id, + { + "status": OrderStatus.CLOSED.value, + "filled": 1000, + } + ) + trade = trade_service.find({"order_id": order_id}) + self.assertEqual(1000, trade.amount) + self.assertEqual(0, trade.remaining) + self.assertEqual(TradeStatus.CLOSED.value, trade.status) + self.assertEqual(0.2, trade.open_price) + self.assertAlmostEqual(1000 * 0.3 - 1000 * 0.2, trade.net_gain) + self.assertEqual(2, len(trade.orders)) + + def test_trade_closing_winning_trade(self): + order_service = self.app.container.order_service() + buy_order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 1000, + "order_side": "BUY", + "price": 0.2, + "order_type": "LIMIT", + "portfolio_id": 1, + "status": "CREATED", + } + ) + updated_buy_order = order_service.update( + buy_order.id, + { + "status": "CLOSED", + "filled": 1000, + "remaining": 0, + } + ) + self.assertEqual(updated_buy_order.amount, 1000) + self.assertEqual(updated_buy_order.filled, 1000) + self.assertEqual(updated_buy_order.remaining, 0) + + # Create a sell order with a higher price + sell_order = order_service.create( + { + "target_symbol": "ADA", + "trading_symbol": "EUR", + "amount": 1000, + "order_side": "SELL", + "price": 0.3, + "order_type": "LIMIT", + "portfolio_id": 1, + "status": "CREATED", + } + ) + self.assertEqual(0.3, sell_order.get_price()) + updated_sell_order = order_service.update( + sell_order.id, + { + "status": "CLOSED", + "filled": 1000, + "remaining": 0, + } + ) + updated_sell_order = order_service.get(sell_order.id) + self.assertEqual(0.3, updated_sell_order.get_price()) + self.assertEqual(updated_sell_order.amount, 1000) + self.assertEqual(updated_sell_order.filled, 1000) + self.assertEqual(updated_sell_order.remaining, 0) + + trade = self.app.container.trade_service().find( + {"order_id": buy_order.id} + ) + self.assertEqual(trade.status, "CLOSED") + self.assertIsNotNone(trade.closed_at) + self.assertIsNotNone(trade.net_gain) + + # def test_trade_closing_losing_trade(self): + # order_service = self.app.container.order_service() + # buy_order = order_service.create( + # { + # "target_symbol": "ADA", + # "trading_symbol": "EUR", + # "amount": 1000, + # "order_side": "BUY", + # "price": 0.2, + # "order_type": "LIMIT", + # "portfolio_id": 1, + # "status": "CREATED", + # } + # ) + # updated_buy_order = order_service.update( + # buy_order.id, + # { + # "status": "CLOSED", + # "filled": 1000, + # "remaining": 0, + # } + # ) + # self.assertEqual(updated_buy_order.amount, 1000) + # self.assertEqual(updated_buy_order.filled, 1000) + # self.assertEqual(updated_buy_order.remaining, 0) + + # # Create a sell order with a higher price + # sell_order = order_service.create( + # { + # "target_symbol": "ADA", + # "trading_symbol": "EUR", + # "amount": 1000, + # "order_side": "SELL", + # "price": 0.1, + # "order_type": "LIMIT", + # "portfolio_id": 1, + # "status": "CREATED", + # } + # ) + # self.assertEqual(0.1, sell_order.get_price()) + # updated_sell_order = order_service.update( + # sell_order.id, + # { + # "status": "CLOSED", + # "filled": 1000, + # "remaining": 0, + # } + # ) + # self.assertEqual(0.1, updated_sell_order.get_price()) + # self.assertEqual(updated_sell_order.amount, 1000) + # self.assertEqual(updated_sell_order.filled, 1000) + # self.assertEqual(updated_sell_order.remaining, 0) + # buy_order = order_service.get(buy_order.id) + # self.assertEqual(buy_order.status, "CLOSED") + # self.assertIsNotNone(buy_order.get_trade_closed_at()) + # self.assertIsNotNone(buy_order.get_trade_closed_price()) + # self.assertEqual(-100, buy_order.get_net_gain()) \ No newline at end of file diff --git a/tests/test_create_app.py b/tests/test_create_app.py index d33fafc7..378a3b42 100644 --- a/tests/test_create_app.py +++ b/tests/test_create_app.py @@ -69,6 +69,7 @@ def test_create_app_web(self): app.container.market_service.override( market_service ) + app.initialize_config() app.initialize() self.assertIsNotNone(app) self.assertIsNotNone(app._flask_app)