diff --git a/cli.py b/cli.py
index 5ca3a41..e867b40 100644
--- a/cli.py
+++ b/cli.py
@@ -41,6 +41,6 @@ def run_optimize(args, logger):
if args.command == 'train':
trader.train(n_epochs=args.epochs)
elif args.command == 'test':
- trader.test(model_epoch=args.model_epoch, should_render=args.no_render)
+ trader.test(model_epoch=args.model_epoch, should_render=args.no_render, render_tearsheet=args.no_tearsheet)
elif args.command == 'update-static-data':
download_data_async()
diff --git a/data/.DS_Store b/data/.DS_Store
new file mode 100644
index 0000000..7871d54
Binary files /dev/null and b/data/.DS_Store differ
diff --git a/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__0.html b/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__0.html
new file mode 100644
index 0000000..cf056a5
--- /dev/null
+++ b/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__0.html
@@ -0,0 +1,37495 @@
+
+
+
+
+
+
+
+
+ Tearsheet (generated by QuantStats)
+
+
+
+
+
+
+
+
+
+
Strategy Tearsheet 7 Feb, 2019 - 4 Jul, 2019
+
Generated by QuantStats (v. 0.0.16)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Key Performance Metrics
+
+
+Metric | Strategy |
+
+
+Risk-Free Rate | 0.0% |
+Time in Market | 100.0% |
+
|
+Cumulative Return | 198.59% |
+CAGR% | 98.59% |
+Sharpe | 0.72 |
+Sortino | 1.1 |
+Max Drawdown | -26.53% |
+Longest DD Days | 36 |
+Volatility (ann.) | 11.84% |
+Calmar | 3.72 |
+Skew | 1.18 |
+Kurtosis | 36.45 |
+
|
+Expected Daily % | 0.03% |
+Expected Monthly % | 20.0% |
+Expected Yearly % | 198.59% |
+Kelly Criterion | 8.01% |
+Risk of Ruin | 0.0% |
+Daily Value-at-Risk | -1.19% |
+Expected Shortfall (cVaR) | -2.12% |
+
|
+Payoff Ratio | 1.09 |
+Profit Factor | 0.18 |
+Common Sense Ratio | 0.21 |
+CPC Index | 0.1 |
+Tail Ratio | 1.16 |
+Outlier Win Ratio | 5.43 |
+Outlier Loss Ratio | 5.48 |
+
|
+MTD | 8.65% |
+3M | 103.93% |
+6M | 198.59% |
+YTD | 198.59% |
+1Y | 198.59% |
+3Y (ann.) | 98.59% |
+5Y (ann.) | 98.59% |
+10Y (ann.) | 98.59% |
+All-time (ann.) | 98.59% |
+
|
+Best Day | 12.22% |
+Worst Day | -7.8% |
+Best Month | 51.21% |
+Worst Month | 5.16% |
+Best Year | 198.59% |
+Worst Year | 198.59% |
+
|
+Avg. Drawdown | -2.79% |
+Avg. Drawdown Days | 2 |
+Recovery Factor | 7.48 |
+Ulcer Index | 1.0 |
+
|
+Avg. Up Month | 20.95% |
+Avg. Down Month | nan% |
+Win Days % | 52.08% |
+Win Month % | 100.0% |
+Win Quarter % | 100.0% |
+Win Year % | 100.0% |
+
+
+
+
EOY Returns
+
+
+Year | Return | Cumulative |
+
+
+2019 | 119.13% | 198.59% |
+
+
+
+
+
Worst 10 Drawdowns
+
+
+Started | Recovered | Drawdown | Days |
+
+
+2019-06-26 | 2019-07-04 | -26.53 | 7 |
+2019-05-30 | 2019-06-16 | -16.81 | 16 |
+2019-05-16 | 2019-05-26 | -15.45 | 10 |
+2019-02-24 | 2019-04-02 | -12.04 | 36 |
+2019-04-24 | 2019-05-03 | -9.54 | 9 |
+2019-04-03 | 2019-04-10 | -8.70 | 6 |
+2019-05-12 | 2019-05-13 | -8.25 | 1 |
+2019-04-10 | 2019-04-23 | -8.01 | 12 |
+2019-06-22 | 2019-06-25 | -5.76 | 2 |
+2019-06-17 | 2019-06-20 | -4.84 | 3 |
+
+
+
+
+
+
+
+
+
diff --git a/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__1.html b/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__1.html
new file mode 100644
index 0000000..eb90e22
--- /dev/null
+++ b/data/reports/PPO2__MlpLnLstmPolicy__IncrementalProfit__1.html
@@ -0,0 +1,37344 @@
+
+
+
+
+
+
+
+
+ Tearsheet (generated by QuantStats)
+
+
+
+
+
+
+
+
+
+
Strategy Tearsheet 7 Feb, 2019 - 4 Jul, 2019
+
Generated by QuantStats (v. 0.0.16)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Key Performance Metrics
+
+
+Metric | Strategy |
+
+
+Risk-Free Rate | 0.0% |
+Time in Market | 100.0% |
+
|
+Cumulative Return | 170.5% |
+CAGR% | 70.5% |
+Sharpe | 0.67 |
+Sortino | 1.01 |
+Max Drawdown | -25.95% |
+Longest DD Days | 37 |
+Volatility (ann.) | 11.61% |
+Calmar | 2.72 |
+Skew | 0.64 |
+Kurtosis | 25.73 |
+
|
+Expected Daily % | 0.03% |
+Expected Monthly % | 18.04% |
+Expected Yearly % | 170.5% |
+Kelly Criterion | 7.38% |
+Risk of Ruin | 0.0% |
+Daily Value-at-Risk | -1.17% |
+Expected Shortfall (cVaR) | -2.11% |
+
|
+Payoff Ratio | 1.09 |
+Profit Factor | 0.17 |
+Common Sense Ratio | 0.2 |
+CPC Index | 0.09 |
+Tail Ratio | 1.18 |
+Outlier Win Ratio | 5.31 |
+Outlier Loss Ratio | 5.71 |
+
|
+MTD | 4.22% |
+3M | 91.48% |
+6M | 170.5% |
+YTD | 170.5% |
+1Y | 170.5% |
+3Y (ann.) | 70.5% |
+5Y (ann.) | 70.5% |
+10Y (ann.) | 70.5% |
+All-time (ann.) | 70.5% |
+
|
+Best Day | 9.82% |
+Worst Day | -7.73% |
+Best Month | 54.81% |
+Worst Month | 4.22% |
+Best Year | 170.5% |
+Worst Year | 170.5% |
+
|
+Avg. Drawdown | -2.84% |
+Avg. Drawdown Days | 2 |
+Recovery Factor | 6.57 |
+Ulcer Index | 1.0 |
+
|
+Avg. Up Month | 19.17% |
+Avg. Down Month | nan% |
+Win Days % | 51.75% |
+Win Month % | 100.0% |
+Win Quarter % | 100.0% |
+Win Year % | 100.0% |
+
+
+
+
EOY Returns
+
+
+Year | Return | Cumulative |
+
+
+2019 | 108.9% | 170.5% |
+
+
+
+
+
Worst 10 Drawdowns
+
+
+Started | Recovered | Drawdown | Days |
+
+
+2019-06-26 | 2019-07-04 | -25.95 | 7 |
+2019-05-30 | 2019-06-20 | -17.30 | 21 |
+2019-05-16 | 2019-05-26 | -14.67 | 10 |
+2019-02-24 | 2019-04-02 | -10.60 | 37 |
+2019-04-24 | 2019-05-03 | -9.21 | 9 |
+2019-04-10 | 2019-04-23 | -8.14 | 12 |
+2019-05-12 | 2019-05-13 | -7.98 | 1 |
+2019-04-03 | 2019-04-08 | -7.80 | 4 |
+2019-05-14 | 2019-05-16 | -7.24 | 1 |
+2019-06-22 | 2019-06-25 | -5.70 | 2 |
+
+
+
+
+
+
+
+
+
diff --git a/lib/RLTrader.py b/lib/RLTrader.py
index 850be21..bf70d0e 100644
--- a/lib/RLTrader.py
+++ b/lib/RLTrader.py
@@ -1,6 +1,8 @@
import os
import optuna
import numpy as np
+import pandas as pd
+import quantstats as qs
from os import path
from typing import Dict
@@ -154,7 +156,7 @@ def optimize_params(self, trial, n_prune_evals_per_trial: int = 2, n_tests_per_e
trades = train_env.get_attr('trades')
if len(trades[0]) < 1:
- self.logger.info('Pruning trial for not making any trades: ', eval_idx)
+ self.logger.info(f'Pruning trial for not making any trades: {eval_idx}')
raise optuna.structs.TrialPruned()
state = None
@@ -195,7 +197,7 @@ def optimize(self, n_trials: int = 20, *optimize_params):
return self.optuna_study.trials_dataframe()
- def train(self, n_epochs: int = 10, save_every: int = 1, test_trained_model: bool = False, render_trained_model: bool = False):
+ def train(self, n_epochs: int = 10, save_every: int = 1, test_trained_model: bool = True, render_trained_model: bool = False, save_results: bool = True):
train_provider, test_provider = self.data_provider.split_data_train_test(self.train_split_percentage)
del test_provider
@@ -221,11 +223,11 @@ def train(self, n_epochs: int = 10, save_every: int = 1, test_trained_model: boo
model.save(model_path)
if test_trained_model:
- self.test(model_epoch, should_render=render_trained_model)
+ self.test(model_epoch, should_render=render_trained_model, render_tearsheet=False, save_tearsheet=save_results)
self.logger.info(f'Trained {n_epochs} models')
- def test(self, model_epoch: int = 0, should_render: bool = True):
+ def test(self, model_epoch: int = 0, should_render: bool = True, render_tearsheet: bool = True, save_tearsheet: bool = False):
train_provider, test_provider = self.data_provider.split_data_train_test(self.train_split_percentage)
del train_provider
@@ -247,7 +249,7 @@ def test(self, model_epoch: int = 0, should_render: bool = True):
for _ in range(len(test_provider.data_frame)):
action, state = model.predict(zero_completed_obs, state=state)
- obs, reward, _, __ = test_env.step([action[0]])
+ obs, reward, done, info = test_env.step([action[0]])
zero_completed_obs[0, :] = obs
@@ -256,5 +258,17 @@ def test(self, model_epoch: int = 0, should_render: bool = True):
if should_render:
test_env.render(mode='human')
+ if done:
+ net_worths = pd.DataFrame(
+ {'Date': info[0]['timestamps'],
+ 'Balance': info[0]['networths'],
+ })
+ net_worths.set_index('Date', drop=True, inplace=True)
+ returns = net_worths.pct_change()[1:]
+ if(render_tearsheet):
+ qs.plots.snapshot(returns.Balance, title='RL Trader Performance')
+ if(save_tearsheet):
+ reports_path = path.join('data', 'reports', f'{self.study_name}__{model_epoch}.html')
+ qs.reports.html(returns.Balance, file=reports_path)
self.logger.info(
f'Finished testing model ({self.study_name}__{model_epoch}): ${"{:.2f}".format(np.sum(rewards))}')
diff --git a/lib/cli/RLTraderCLI.py b/lib/cli/RLTraderCLI.py
index 1b21ebc..869ace8 100644
--- a/lib/cli/RLTraderCLI.py
+++ b/lib/cli/RLTraderCLI.py
@@ -55,6 +55,7 @@ def __init__(self):
test_parser = subparsers.add_parser('test', description='Test model')
test_parser.add_argument('--model-epoch', type=int, default=1, help='Model epoch index')
test_parser.add_argument('--no-render', action='store_false', help='Do not render test')
+ test_parser.add_argument('--no-tearsheet', action='store_false', help='Do not render tearsheet')
subparsers.add_parser('update-static-data', description='Update static data')
diff --git a/lib/data/features/indicators.py b/lib/data/features/indicators.py
index 027af49..b5e8880 100644
--- a/lib/data/features/indicators.py
+++ b/lib/data/features/indicators.py
@@ -35,10 +35,10 @@
('KCLI', ta.keltner_channel_lband_indicator, ['High', 'Low', 'Close']),
('DCHI', ta.donchian_channel_hband_indicator, ['Close']),
('DCLI', ta.donchian_channel_lband_indicator, ['Close']),
- ('ADI', ta.acc_dist_index, ['High', 'Low', 'Close', 'volume']),
- ('OBV', ta.on_balance_volume, ['close', 'volume']),
+ ('ADI', ta.acc_dist_index, ['High', 'Low', 'Close', 'Volume BTC']),
+ ('OBV', ta.on_balance_volume, ['Close', 'Volume BTC']),
('CMF', ta.chaikin_money_flow, ['High', 'Low', 'Close', 'Volume BTC']),
- ('FI', ta.force_index, ['Close', 'Volume']),
+ ('FI', ta.force_index, ['Close', 'Volume BTC']),
('EM', ta.ease_of_movement, ['High', 'Low', 'Close', 'Volume BTC']),
('VPT', ta.volume_price_trend, ['Close', 'Volume BTC']),
('NVI', ta.negative_volume_index, ['Close', 'Volume BTC']),
diff --git a/lib/env/TradingEnv.py b/lib/env/TradingEnv.py
index 9f7e8d9..d827ddc 100644
--- a/lib/env/TradingEnv.py
+++ b/lib/env/TradingEnv.py
@@ -115,7 +115,6 @@ def _take_action(self, action: int):
current_net_worth = round(self.balance + self.asset_held * self._current_price(), self.base_precision)
self.net_worths.append(current_net_worth)
-
self.account_history = self.account_history.append({
'balance': self.balance,
'asset_bought': asset_bought,
@@ -155,6 +154,7 @@ def _reward(self):
def _next_observation(self):
self.current_ohlcv = self.data_provider.next_ohlcv()
+ self.timestamps.append(pd.to_datetime(self.current_ohlcv.Date.item(), unit='s'))
self.observations = self.observations.append(self.current_ohlcv, ignore_index=True)
if self.stationarize_obs:
@@ -187,6 +187,7 @@ def reset(self):
self.balance = self.initial_balance
self.net_worths = [self.initial_balance]
+ self.timestamps = []
self.asset_held = 0
self.current_step = 0
@@ -210,8 +211,7 @@ def step(self, action):
obs = self._next_observation()
reward = self._reward()
done = self._done()
-
- return obs, reward, done, {}
+ return obs, reward, done, {'networths': self.net_worths, 'timestamps': self.timestamps}
def render(self, mode='human'):
if mode == 'system':
diff --git a/requirements.base.txt b/requirements.base.txt
index f352090..3b178e5 100644
--- a/requirements.base.txt
+++ b/requirements.base.txt
@@ -10,4 +10,5 @@ statsmodels==0.10.0rc2
empyrical
ccxt
psycopg2
-configparser
\ No newline at end of file
+configparser
+quantstats