diff --git a/build_ui.sh b/build_ui.sh index d9ca8e7..2bf8775 100755 --- a/build_ui.sh +++ b/build_ui.sh @@ -6,6 +6,7 @@ /usr/bin/pyuic5 ui/newdatasourcedialog.ui > src/nailab/ui_gen/newdatasourcedialog.py /usr/bin/pyuic5 ui/strategywidget.ui > src/nailab/ui_gen/strategywidget.py /usr/bin/pyuic5 ui/tradeslistwidget.ui > src/nailab/ui_gen/tradeslistwidget.py +/usr/bin/pyuic5 ui/performancewidget.ui > src/nailab/ui_gen/performancewidget.py /usr/bin/pyrcc5 nailab.qrc -o src/nailab/nailab_rc.py diff --git a/src/nailab/execution/portfolioexecutor.py b/src/nailab/execution/portfolioexecutor.py new file mode 100644 index 0000000..7f7ceab --- /dev/null +++ b/src/nailab/execution/portfolioexecutor.py @@ -0,0 +1,155 @@ + + +import sys +import importlib +from importlib.machinery import SourceFileLoader +import inspect + +from naiback.strategy import Strategy + +import numpy as np +import math + +def render_float(a): + return "{:.3f}".format(a) + + +def render_ratio(a, b): + if b != 0: + return a / b + else: + return 0 + +class PortfolioExecutor: + + def __init__(self): + pass + + def execute_from_file(self, path, feeds, extents=None): + if "execution._current_strategy" in sys.modules: + del sys.modules["execution._current_strategy"] + loader = SourceFileLoader('execution._current_strategy', path) + mod = loader.load_module() + for item in inspect.getmembers(mod, inspect.isclass): + klass = item[1] + if klass is not Strategy and issubclass(klass, Strategy): + all_trades = [] + for feed in feeds: + strategy = klass() + strategy.add_feed(feed) + + if extents is None: + strategy.run() + else: + strategy.run(from_time=extents[0], to_time=extents[1]) + + trades = strategy.get_analyzer('tradeslist').get_result() + all_trades = self.merge_trades(all_trades, trades) + + results = self.calc_stats(all_trades) + equity = self.calc_equity(all_trades) + return (results, all_trades, equity) + + def merge_trades(self, trades1, trades2): + all_trades = list(sorted(trades1 + trades2, key=lambda x: x['entry_time'])) + result = [] + current_trades = [] + max_trades = 3 + for trade in all_trades: + if len(current_trades) < max_trades: + current_trades.append(trade) + result.append(trade) + new_current_trades = [] + for ct in current_trades: + if ct['exit_time'] < trade['entry_time']: + new_current_trades.append(ct) + else: + # possibly adjust the balance + pass + current_trades = new_current_trades + return result + + def calc_stats(self, trades): + longs = list(filter(lambda x: x['is_long'], trades)) + shorts = list(filter(lambda x: not x['is_long'], trades)) + + result = { 'all' : {}, 'long' : {}, 'short' : {} } + result['all']['net_profit'] = sum([t['pnl'] for t in trades]) + result['long']['net_profit'] = sum([t['pnl'] for t in longs]) + result['short']['net_profit'] = sum([t['pnl'] for t in shorts]) + + result['all']['bars_in_trade'] = 0 # TODO? + result['long']['bars_in_trade'] = 0 + result['short']['bars_in_trade'] = 0 + + result['all']['profit_per_bar'] = 0 + result['long']['profit_per_bar'] = 0 + result['short']['profit_per_bar'] = 0 + + result['all']['number_of_trades'] = len(trades) + result['long']['number_of_trades'] = len(longs) + result['short']['number_of_trades'] = len(shorts) + + result['all']['avg'] = render_ratio(result['all']['net_profit'], result['all']['number_of_trades']) + result['long']['avg'] = render_ratio(result['long']['net_profit'], result['long']['number_of_trades']) + result['short']['avg'] = render_ratio(result['short']['net_profit'], result['short']['number_of_trades']) + + result['all']['avg_percentage'] = render_ratio(sum([t['profit_percentage'] for t in trades]), result['all']['number_of_trades']) + result['long']['avg_percentage'] = render_ratio(sum([t['profit_percentage'] for t in longs]), result['long']['number_of_trades']) + result['short']['avg_percentage'] = render_ratio(sum([t['profit_percentage'] for t in shorts]), result['short']['number_of_trades']) + + result['all']['avg_bars'] = render_ratio(result['all']['bars_in_trade'], result['all']['number_of_trades']) + result['long']['avg_bars'] = render_ratio(result['long']['bars_in_trade'], result['long']['number_of_trades']) + result['short']['avg_bars'] = render_ratio(result['short']['bars_in_trade'], result['short']['number_of_trades']) + + result['all']['won'] = len(list(filter(lambda t: t['pnl'] > 0, trades))) + result['long']['won'] = len(list(filter(lambda t: t['pnl'] > 0, longs))) + result['short']['won'] = len(list(filter(lambda t: t['pnl'] > 0, shorts))) + + result['all']['lost'] = len(list(filter(lambda t: t['pnl'] <= 0, trades))) + result['long']['lost'] = len(list(filter(lambda t: t['pnl'] <= 0, longs))) + result['short']['lost'] = len(list(filter(lambda t: t['pnl'] <= 0, shorts))) + + result['all']['total_won'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] > 0, trades))) + result['long']['total_won'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] > 0, longs))) + result['short']['total_won'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] > 0, shorts))) + + result['all']['total_lost'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] <= 0, trades))) + result['long']['total_lost'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] <= 0, longs))) + result['short']['total_lost'] = sum(map(lambda t: t['pnl'], filter(lambda t: t['pnl'] <= 0, shorts))) + + result['all']['profit_factor'] = render_ratio(result['all']['total_won'], -result['all']['total_lost']) + result['long']['profit_factor'] = render_ratio(result['long']['total_won'], -result['long']['total_lost']) + result['short']['profit_factor'] = render_ratio(result['short']['total_won'], -result['short']['total_lost']) + + mean = np.mean(list(map(lambda x: x['pnl'], trades))) + stddev = np.std(list(map(lambda x: x['pnl'], trades))) + sharpe = mean / stddev + tstat = sharpe * math.sqrt(len(trades)) + result['all']['sharpe_ratio'] = sharpe + result['all']['t_stat'] = tstat + + mean = np.mean(list(map(lambda x: x['pnl'], longs))) + stddev = np.std(list(map(lambda x: x['pnl'], longs))) + sharpe = mean / stddev + tstat = sharpe * math.sqrt(len(longs)) + result['long']['sharpe_ratio'] = sharpe + result['long']['t_stat'] = tstat + + mean = np.mean(list(map(lambda x: x['pnl'], shorts))) + stddev = np.std(list(map(lambda x: x['pnl'], shorts))) + sharpe = mean / stddev + tstat = sharpe * math.sqrt(len(shorts)) + result['short']['sharpe_ratio'] = sharpe + result['short']['t_stat'] = tstat + + return result + + def calc_equity(self, trades): + sorted_trades = sorted(trades, key=lambda x: x['exit_time']) + equity = [] + current = 0 + for t in sorted_trades: + current += t['pnl'] + equity.append(current) + diff --git a/src/nailab/nailab_rc.py b/src/nailab/nailab_rc.py index 5e44e4b..7ca94cf 100644 --- a/src/nailab/nailab_rc.py +++ b/src/nailab/nailab_rc.py @@ -9,7 +9,7 @@ from PyQt5 import QtCore qt_resource_data = b"\ -\x00\x00\x02\x33\ +\x00\x00\x02\x0e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ @@ -17,36 +17,33 @@ qt_resource_data = b"\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x1b\xaf\x00\x00\x1b\xaf\ \x01\x5e\x1a\x91\x1c\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\xb0\x49\x44\ -\x41\x54\x68\x81\xed\x9a\x31\x6a\x02\x41\x14\x86\xff\x59\x05\x25\ -\x8d\x21\x10\xc1\xca\x23\xd8\x06\x1b\x21\x5d\xae\x13\x1b\xab\x14\ -\xc1\xc2\x2a\xe7\xf0\x14\x42\x9a\x25\x6d\x4e\x10\x2c\x12\x83\x1b\ -\x16\x5d\x31\xba\xee\xcc\xbc\x14\x21\x90\x80\x6f\xd0\xe8\xfa\x58\ -\x98\xaf\xdc\x19\xd8\xef\x73\xc6\xb1\x18\x15\x11\xa1\xc8\x04\xd2\ -\x02\x87\x52\x76\x0d\x8e\xc2\xb0\x09\x1b\x0c\x08\xd4\x51\x50\x8d\ -\x53\x49\x01\x00\x81\x26\x0a\xea\x11\x81\xed\x5d\xb7\xdb\x63\x6e\ -\x9e\xe2\xb6\xd0\x28\x0c\x9b\xa0\xe0\x19\x84\x5a\x6e\x96\xbb\xa0\ -\x30\x87\xb2\x2d\x2e\x82\xdf\x42\x36\x18\x88\xcb\x03\x00\xa1\x06\ -\x1b\x0c\xb8\x61\x36\x80\x40\x9d\x7c\x8c\xf6\xc7\xe5\xc2\x06\x9c\ -\x7a\xcf\xbb\x70\xb9\x14\xfe\x14\xf2\x01\xd2\xf8\x00\x69\x7c\x80\ -\x34\x3e\x40\x1a\x1f\x20\x8d\x0f\x90\xc6\x07\x48\xe3\x03\xa4\xf1\ -\x01\xd2\xf8\x00\x69\x7c\x80\x34\x3e\x40\x1a\x1f\x20\x0d\x1b\xa0\ -\x8d\x39\xa5\x87\x13\x97\x0b\x1b\xf0\x99\xae\x73\x91\xf9\x0f\x2e\ -\x17\x36\x20\x9a\xc5\xb0\xd6\xe6\x22\xb4\x0f\xd6\x5a\x44\xb3\x98\ -\x1d\x67\xaf\x98\x32\xad\xf1\xf2\xfe\x8a\xcb\xf3\x0b\x9c\x55\xaa\ -\x28\x97\x4a\xb9\x08\x72\x6c\xb2\x0d\xe6\x49\x82\x24\x5d\x21\xd3\ -\x9a\x9d\xe7\xbc\x23\xcb\xb4\xc6\xdb\xc7\xf4\xe8\x72\x00\x30\x9d\ -\x4c\x76\x9a\x57\x6f\xb8\xaf\x29\x0a\x7f\x0a\xb1\x2b\x60\xad\x45\ -\x10\xe4\xdf\x77\x7b\x75\xbf\xf5\xf9\xc3\xd3\xdd\x1f\x17\x0e\xd6\ -\x70\x93\xa6\x07\x68\x1d\x17\x97\x0b\xbb\x02\xcb\xc5\x62\x5a\xa9\ -\x56\xeb\x4a\xa9\x5c\xa4\x7e\xf8\xfd\x49\x6f\x83\x88\xb0\x5c\x2c\ -\xd8\x2f\x22\xbb\x02\xc6\xe8\x51\x1c\x45\x58\xaf\x56\xa2\xc7\x69\ -\x1c\x45\x30\x46\x8f\xb8\x71\x76\x05\x34\xd0\x53\xc6\xdc\x24\xb3\ -\x99\xe8\x5d\xb1\x35\x66\xae\x81\x1e\x37\xce\xae\x40\xbf\xdb\x1d\ -\x67\xa0\x16\x40\x43\x05\xec\x76\xe6\x1d\x91\xef\x77\xd2\x30\x03\ -\xb5\xfa\xdd\xee\xfe\x7f\x35\x28\x0a\x85\xff\x1d\xf8\x02\xd1\xfa\ -\xa2\xe7\x75\x3b\xf6\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8b\x49\x44\ +\x41\x54\x68\x81\xed\x99\x4f\x4a\xc3\x40\x14\x87\x7f\x49\x53\x94\ +\x0a\x2a\xa2\xe2\x9f\x45\xf1\x04\x3d\x42\xc5\x9d\x1b\x71\x29\x81\ +\x9e\xa0\x17\xe8\x11\x7a\x0f\x37\x5d\x8a\x07\x08\xba\xc9\xde\x13\ +\x94\x2e\x6a\x4b\x5a\x28\xad\x34\x28\x49\xe6\x79\x82\x37\x24\x99\ +\xc6\x17\x61\xbe\xed\x1b\xde\xfc\x3e\xe6\x31\x09\x8c\x43\x44\xf8\ +\xcf\xb8\xd2\x01\x4c\xf1\x74\xc5\x20\x0c\xdb\x50\xee\x90\x40\x5d\ +\x07\xce\xe5\x2e\x37\x9e\x2e\xa3\xf7\xde\xe3\xc3\xad\x69\x1f\xf6\ +\x04\x82\x30\x6c\x83\xdc\x0f\x00\x4f\xbb\x0e\x0f\x00\x5f\xf1\xb6\ +\xfb\xfc\xf2\xfa\x66\xda\x87\x1f\x21\xe5\x0e\x41\x38\x32\xdd\x40\ +\xc7\x2e\x24\x58\x01\x02\xdd\x99\x34\xce\x8b\xa9\x04\x2b\xe0\xc0\ +\x39\x2f\xdb\xb4\x28\x26\x12\xb5\xb9\x85\xca\x4a\xd4\x46\x00\x28\ +\x27\x51\x2b\x01\xa0\xb8\x84\x98\x80\xd7\x68\xb0\xb5\x22\x12\x62\ +\x02\xad\xbd\x7d\x6d\x3d\xaf\x84\x98\xc0\xd9\xf1\x09\x5c\x57\xbf\ +\x7d\x1e\x09\x31\x81\xa6\xe7\xe1\xe6\xe2\x1a\x87\xad\x03\xa3\x71\ +\xd2\xfe\x0b\x55\x4d\xd3\xf3\x70\x75\x9a\xeb\x73\xd3\xe5\x0a\xb5\ +\xbb\x85\x8a\x62\x05\xa4\xb1\x02\xd2\x58\x01\x69\xac\x80\x34\x56\ +\x40\x1a\x2b\x20\x8d\x15\x90\xc6\x0a\x48\x63\x05\xa4\xb1\x02\xd2\ +\x58\x01\x69\xac\x80\x34\x56\x40\x1a\x56\x20\xcd\xb2\xbf\xcc\xa1\ +\x45\x97\x85\x15\x88\x7f\xbe\x2b\x09\x53\x06\x5d\x16\x56\x60\xb1\ +\x5e\x45\x4a\xa9\x4a\x02\x15\x41\x29\x85\xc5\x7a\x15\x71\x75\x7e\ +\x84\x92\x24\x18\xcf\xa7\xd8\xc4\x5b\x91\x71\x4a\xb3\x0c\x9b\x78\ +\x8b\xf1\x7c\x8a\x34\x49\x02\x6e\x1d\xfb\x3e\xa0\x40\x83\x24\x4d\ +\xef\x3f\x97\x51\xa5\xaf\xf5\x39\x58\x13\x68\xc0\x15\xd9\x13\xe8\ +\xfb\xfe\x84\x40\x1d\x07\x18\x01\x98\x55\x12\x4d\xcf\xcc\x01\x46\ +\x04\xea\xf4\x7d\x7f\xc2\x2d\xfa\x05\x0e\x3c\xa3\x06\xde\xce\x5a\ +\xc4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x8f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -90,6 +87,44 @@ qt_resource_data = b"\ \x41\xe9\x31\x00\xcb\x38\xfb\x94\x19\x77\x07\x00\x96\xa1\xf4\x58\ \x31\x9f\x2d\x91\xfb\xb7\x8a\x65\x3f\x00\xdf\xf9\xa7\x43\x02\xf4\ \x78\xd9\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x33\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x1b\xaf\x00\x00\x1b\xaf\ +\x01\x5e\x1a\x91\x1c\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\xb0\x49\x44\ +\x41\x54\x68\x81\xed\x9a\x31\x6a\x02\x41\x14\x86\xff\x59\x05\x25\ +\x8d\x21\x10\xc1\xca\x23\xd8\x06\x1b\x21\x5d\xae\x13\x1b\xab\x14\ +\xc1\xc2\x2a\xe7\xf0\x14\x42\x9a\x25\x6d\x4e\x10\x2c\x12\x83\x1b\ +\x16\x5d\x31\xba\xee\xcc\xbc\x14\x21\x90\x80\x6f\xd0\xe8\xfa\x58\ +\x98\xaf\xdc\x19\xd8\xef\x73\xc6\xb1\x18\x15\x11\xa1\xc8\x04\xd2\ +\x02\x87\x52\x76\x0d\x8e\xc2\xb0\x09\x1b\x0c\x08\xd4\x51\x50\x8d\ +\x53\x49\x01\x00\x81\x26\x0a\xea\x11\x81\xed\x5d\xb7\xdb\x63\x6e\ +\x9e\xe2\xb6\xd0\x28\x0c\x9b\xa0\xe0\x19\x84\x5a\x6e\x96\xbb\xa0\ +\x30\x87\xb2\x2d\x2e\x82\xdf\x42\x36\x18\x88\xcb\x03\x00\xa1\x06\ +\x1b\x0c\xb8\x61\x36\x80\x40\x9d\x7c\x8c\xf6\xc7\xe5\xc2\x06\x9c\ +\x7a\xcf\xbb\x70\xb9\x14\xfe\x14\xf2\x01\xd2\xf8\x00\x69\x7c\x80\ +\x34\x3e\x40\x1a\x1f\x20\x8d\x0f\x90\xc6\x07\x48\xe3\x03\xa4\xf1\ +\x01\xd2\xf8\x00\x69\x7c\x80\x34\x3e\x40\x1a\x1f\x20\x0d\x1b\xa0\ +\x8d\x39\xa5\x87\x13\x97\x0b\x1b\xf0\x99\xae\x73\x91\xf9\x0f\x2e\ +\x17\x36\x20\x9a\xc5\xb0\xd6\xe6\x22\xb4\x0f\xd6\x5a\x44\xb3\x98\ +\x1d\x67\xaf\x98\x32\xad\xf1\xf2\xfe\x8a\xcb\xf3\x0b\x9c\x55\xaa\ +\x28\x97\x4a\xb9\x08\x72\x6c\xb2\x0d\xe6\x49\x82\x24\x5d\x21\xd3\ +\x9a\x9d\xe7\xbc\x23\xcb\xb4\xc6\xdb\xc7\xf4\xe8\x72\x00\x30\x9d\ +\x4c\x76\x9a\x57\x6f\xb8\xaf\x29\x0a\x7f\x0a\xb1\x2b\x60\xad\x45\ +\x10\xe4\xdf\x77\x7b\x75\xbf\xf5\xf9\xc3\xd3\xdd\x1f\x17\x0e\xd6\ +\x70\x93\xa6\x07\x68\x1d\x17\x97\x0b\xbb\x02\xcb\xc5\x62\x5a\xa9\ +\x56\xeb\x4a\xa9\x5c\xa4\x7e\xf8\xfd\x49\x6f\x83\x88\xb0\x5c\x2c\ +\xd8\x2f\x22\xbb\x02\xc6\xe8\x51\x1c\x45\x58\xaf\x56\xa2\xc7\x69\ +\x1c\x45\x30\x46\x8f\xb8\x71\x76\x05\x34\xd0\x53\xc6\xdc\x24\xb3\ +\x99\xe8\x5d\xb1\x35\x66\xae\x81\x1e\x37\xce\xae\x40\xbf\xdb\x1d\ +\x67\xa0\x16\x40\x43\x05\xec\x76\xe6\x1d\x91\xef\x77\xd2\x30\x03\ +\xb5\xfa\xdd\xee\xfe\x7f\x35\x28\x0a\x85\xff\x1d\xf8\x02\xd1\xfa\ +\xa2\xe7\x75\x3b\xf6\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ \x00\x00\x03\x8f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -149,41 +184,6 @@ qt_resource_data = b"\ \x09\xf1\x80\x43\x84\x88\xf8\xc7\x59\x11\x0f\x08\xbf\x13\x4f\x03\ \xd1\x77\xe2\x69\xf0\xdf\xc0\xb4\xf9\x0b\x47\x70\x6d\x4b\x01\x24\ \x0b\x02\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x0e\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x1b\xaf\x00\x00\x1b\xaf\ -\x01\x5e\x1a\x91\x1c\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8b\x49\x44\ -\x41\x54\x68\x81\xed\x99\x4f\x4a\xc3\x40\x14\x87\x7f\x49\x53\x94\ -\x0a\x2a\xa2\xe2\x9f\x45\xf1\x04\x3d\x42\xc5\x9d\x1b\x71\x29\x81\ -\x9e\xa0\x17\xe8\x11\x7a\x0f\x37\x5d\x8a\x07\x08\xba\xc9\xde\x13\ -\x94\x2e\x6a\x4b\x5a\x28\xad\x34\x28\x49\xe6\x79\x82\x37\x24\x99\ -\xc6\x17\x61\xbe\xed\x1b\xde\xfc\x3e\xe6\x31\x09\x8c\x43\x44\xf8\ -\xcf\xb8\xd2\x01\x4c\xf1\x74\xc5\x20\x0c\xdb\x50\xee\x90\x40\x5d\ -\x07\xce\xe5\x2e\x37\x9e\x2e\xa3\xf7\xde\xe3\xc3\xad\x69\x1f\xf6\ -\x04\x82\x30\x6c\x83\xdc\x0f\x00\x4f\xbb\x0e\x0f\x00\x5f\xf1\xb6\ -\xfb\xfc\xf2\xfa\x66\xda\x87\x1f\x21\xe5\x0e\x41\x38\x32\xdd\x40\ -\xc7\x2e\x24\x58\x01\x02\xdd\x99\x34\xce\x8b\xa9\x04\x2b\xe0\xc0\ -\x39\x2f\xdb\xb4\x28\x26\x12\xb5\xb9\x85\xca\x4a\xd4\x46\x00\x28\ -\x27\x51\x2b\x01\xa0\xb8\x84\x98\x80\xd7\x68\xb0\xb5\x22\x12\x62\ -\x02\xad\xbd\x7d\x6d\x3d\xaf\x84\x98\xc0\xd9\xf1\x09\x5c\x57\xbf\ -\x7d\x1e\x09\x31\x81\xa6\xe7\xe1\xe6\xe2\x1a\x87\xad\x03\xa3\x71\ -\xd2\xfe\x0b\x55\x4d\xd3\xf3\x70\x75\x9a\xeb\x73\xd3\xe5\x0a\xb5\ -\xbb\x85\x8a\x62\x05\xa4\xb1\x02\xd2\x58\x01\x69\xac\x80\x34\x56\ -\x40\x1a\x2b\x20\x8d\x15\x90\xc6\x0a\x48\x63\x05\xa4\xb1\x02\xd2\ -\x58\x01\x69\xac\x80\x34\x56\x40\x1a\x56\x20\xcd\xb2\xbf\xcc\xa1\ -\x45\x97\x85\x15\x88\x7f\xbe\x2b\x09\x53\x06\x5d\x16\x56\x60\xb1\ -\x5e\x45\x4a\xa9\x4a\x02\x15\x41\x29\x85\xc5\x7a\x15\x71\x75\x7e\ -\x84\x92\x24\x18\xcf\xa7\xd8\xc4\x5b\x91\x71\x4a\xb3\x0c\x9b\x78\ -\x8b\xf1\x7c\x8a\x34\x49\x02\x6e\x1d\xfb\x3e\xa0\x40\x83\x24\x4d\ -\xef\x3f\x97\x51\xa5\xaf\xf5\x39\x58\x13\x68\xc0\x15\xd9\x13\xe8\ -\xfb\xfe\x84\x40\x1d\x07\x18\x01\x98\x55\x12\x4d\xcf\xcc\x01\x46\ -\x04\xea\xf4\x7d\x7f\xc2\x2d\xfa\x05\x0e\x3c\xa3\x06\xde\xce\x5a\ -\xc4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = b"\ @@ -195,32 +195,32 @@ qt_resource_name = b"\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ -\x00\x08\ -\x08\xc8\x58\x67\ -\x00\x73\ -\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x07\ +\x04\xca\x57\xa7\ +\x00\x6e\ +\x00\x65\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x06\xc1\x59\x87\ \x00\x6f\ \x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x08\ +\x08\xc8\x58\x67\ +\x00\x73\ +\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0b\ \x0c\x81\x80\x07\ \x00\x65\ \x00\x78\x00\x65\x00\x63\x00\x75\x00\x74\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x07\ -\x04\xca\x57\xa7\ -\x00\x6e\ -\x00\x65\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x0e\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\ -\x00\x00\x00\x66\x00\x00\x00\x00\x00\x01\x00\x00\x08\x5d\ -\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x02\x37\ \x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x04\xca\ +\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x02\x12\ +\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x04\xa5\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x06\xdc\ " def qInitResources(): diff --git a/src/nailab/ui/correlationchartcanvas.py b/src/nailab/ui/correlationchartcanvas.py new file mode 100644 index 0000000..30c951e --- /dev/null +++ b/src/nailab/ui/correlationchartcanvas.py @@ -0,0 +1,138 @@ + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.Qsci import * + +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +import matplotlib.pyplot as plt +import matplotlib + +import numpy as np + + +class CorrelationChartCanvas(FigureCanvas): + def __init__(self, parent=None, width=6, height=6, dpi=100): + self.fig = Figure(figsize=(width, height), dpi=dpi) + + FigureCanvas.__init__(self, self.fig) + self.setParent(parent) + + self.trades_series = {} + self.mode = "daily" + + def setMode(self, mode): + if mode not in ["daily", "monthly"]: + return + + self.mode = mode + self._refresh() + + def add_trades_series(self, name, trades): + self.trades_series[name] = trades + self._refresh() + + def add_trades_series_batched(self, trades_list): + for series in trades_list: + self.trades_series[series["name"]] = series["trades"] + self._refresh() + + def remove_trades_series(self, name): + del self.trades_series[name] + self._refresh() + + def _refresh(self): + self.fig.clear() + + if len(self.trades_series) == 0: + return + + corr_matrix = np.zeros((len(self.trades_series), len(self.trades_series))) + x = 0 + y = 0 + names = [] + for n1, trades1 in self.trades_series.items(): + names.append(n1) + for n2, trades2 in self.trades_series.items(): + corr_matrix[x][y] = self._calc_correlation(trades1, trades2) + y += 1 + x += 1 + y = 0 + + ax1 = self.figure.add_subplot(111) + ax1.imshow(corr_matrix, cmap="BrBG", norm=matplotlib.colors.Normalize(vmin=-1.0, vmax=1.0)) + ax1.set_title("Returns correlation") + ax1.set_xticks(np.arange(len(names))) + ax1.set_yticks(np.arange(len(names))) + ax1.set_xticklabels(names) + ax1.set_yticklabels(names) + for i in range(len(self.trades_series)): + for j in range(len(self.trades_series)): + col = 'k' + if corr_matrix[i, j] > 0.7: + col = 'w' + text = ax1.text(j, i, "{:.2f}".format(corr_matrix[i, j]), ha="center", va="center", color=col, fontsize="x-small") + + plt.setp(ax1.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") + + self.draw() + + def _calc_correlation(self, trades1, trades2): + t1 = {} + all_dates = set() + for trade in trades1: + all_dates.add(self._trade_date(trade)) + try: + t1[self._trade_date(trade)] += trade["percentage"] + except KeyError: + t1[self._trade_date(trade)] = trade["percentage"] + + t2 = {} + for trade in trades2: + all_dates.add(self._trade_date(trade)) + try: + t2[self._trade_date(trade)] += trade["percentage"] + except KeyError: + t2[self._trade_date(trade)] = trade["percentage"] + + + ret1 = [] + for d in all_dates: + try: + ret1.append(t1[d]) + except KeyError: + ret1.append(0) + + ret2 = [] + for d in all_dates: + try: + ret2.append(t2[d]) + except KeyError: + ret2.append(0) + + mean1 = np.mean(ret1) + mean2 = np.mean(ret2) + sigma1 = np.std(ret1) + sigma2 = np.std(ret2) + + s = 0 + for i in range(len(all_dates)): + x1 = ret1[i] - mean1 + x2 = ret2[i] - mean2 + s += x1 * x2 + s /= len(all_dates) + s /= (sigma1 * sigma2) + + return s + + + def _trade_date(self, trade): + if self.mode == "daily": + return trade["exit_time"].date() + else: + return trade["exit_time"].date().replace(day=1) + + + + + + diff --git a/src/nailab/ui/mainwindow.py b/src/nailab/ui/mainwindow.py index 9c9787e..d2fabfb 100644 --- a/src/nailab/ui/mainwindow.py +++ b/src/nailab/ui/mainwindow.py @@ -7,6 +7,7 @@ import sys import traceback from execution.executor import Executor +from execution.portfolioexecutor import PortfolioExecutor from data.datasourcemanager import DataSourceManager from PyQt5 import QtCore, QtGui, QtWidgets @@ -14,6 +15,7 @@ from PyQt5.Qsci import * from ui_gen.mainwindow import Ui_MainWindow from ui.strategywidget import StrategyWidget +from ui.performancewidget import PerformanceWidget from naiback.strategy import Strategy from templates.new_strategy import new_strategy_template @@ -23,7 +25,6 @@ class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) - self.sources = [] self.datasourcemanager = DataSourceManager() self.datasourcemanager.load_sources(QtCore.QSettings()) @@ -92,15 +93,34 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.tabs.currentWidget().setError(self.tr("Successful run")) except Exception as e: self.ui.tabs.currentWidget().setError(traceback.format_exc()) + + def executeStrategyInPortfolioMode(self): + source_file = self.ui.tabs.currentWidget().source_file + executor = PortfolioExecutor() + selected_feed_ids = self.ui.tabs.currentWidget().get_selected_feeds() + selected_feeds = [] + for (source_id, feed_id) in selected_feed_ids: + try: + selected_feeds.append(self.datasourcemanager.get_source(source_id).get_feed(feed_id)) + except Exception as e: + self.ui.tabs.currentWidget().setError(traceback.format_exc()) + + try: + result = executor.execute_from_file(source_file, selected_feeds, self.ui.tabs.currentWidget().get_time_window()) + self.ui.tabs.currentWidget().set_result(result) + self.ui.tabs.currentWidget().setError(self.tr("Successful run")) + except Exception as e: + self.ui.tabs.currentWidget().setError(traceback.format_exc()) def tabCloseRequested(self, tab_index): - del self.sources[tab_index] - self.ui.tabs.widget(tab_index).save_state() + if self.ui.tabs.widget(tab_index).widget_type() == "strategy": + self.ui.tabs.widget(tab_index).save_state() self.ui.tabs.removeTab(tab_index) + def performanceAnalysis(self): + self.ui.tabs.addTab(PerformanceWidget(), self.tr("Performance analysis")) + def _makeEditor(self, source_file=None, tab_name="Untitled", content=None): editor = StrategyWidget(self.datasourcemanager, source_file, self, content) - self.sources.append(source_file) self.ui.tabs.addTab(editor, tab_name) editor.savedChanged.connect(self.savedChanged) - diff --git a/src/nailab/ui/performancecanvas.py b/src/nailab/ui/performancecanvas.py new file mode 100644 index 0000000..cd199ff --- /dev/null +++ b/src/nailab/ui/performancecanvas.py @@ -0,0 +1,92 @@ + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.Qsci import * + +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +import matplotlib.pyplot as plt + +import numpy as np + + +class PerformanceCanvas(FigureCanvas): + def __init__(self, parent=None, width=5, height=4, dpi=100): + self.fig = Figure(figsize=(width, height), dpi=dpi) + + FigureCanvas.__init__(self, self.fig) + self.setParent(parent) + + self.trades_series = {} + + + def add_trades_series(self, name, trades): + self.trades_series[name] = trades + self._refresh() + + def add_trades_series_batched(self, trades_list): + for series in trades_list: + self.trades_series[series["name"]] = series["trades"] + self._refresh() + + def remove_trades_series(self, name): + del self.trades_series[name] + self._refresh() + + def _refresh(self): + self.fig.clear() + + if len(self.trades_series) == 0: + return + + all_trades = [] + for k, v in self.trades_series.items(): + for trade in v: + all_trades.append(trade) + + all_trades = sorted(all_trades, key=lambda x: x["exit_time"]) + + cumpnl = np.cumsum(list(map(lambda x: x["percentage"], all_trades))) + + max_equity = 0 + drawdown = [] + for x in cumpnl: + if x > max_equity: + max_equity = x + drawdown.append(0) + else: + drawdown.append(x - max_equity) + + monthly_returns = {} + cur_month = None + cur_pnl = 0 + + for trade in all_trades: + trade_month = trade["exit_time"].strftime("%Y-%m") + if trade_month != cur_month and cur_month is not None: + monthly_returns[cur_month] = cur_pnl + cur_pnl = 0 + cur_month = trade_month + if cur_month is None: + cur_month = trade_month + cur_pnl += trade["percentage"] + + monthly_returns[cur_month] = cur_pnl + monthly_returns_raw = [] + for k, v in monthly_returns.items(): + monthly_returns_raw.append((k, v)) + + monthly_returns_raw = sorted(monthly_returns_raw, key=lambda x: x[0]) + self.monthly_returns_x = list(map(lambda x: x[0], monthly_returns_raw)) + self.monthly_returns_y = list(map(lambda x: x[1], monthly_returns_raw)) + + ax1 = self.figure.add_subplot(211) + ax1.plot(cumpnl, "b-") + ax1.set_title("Cumulative PnL") + ax1_2 = ax1.twinx() + ax1_2.plot(drawdown, "r-") + + ax2 = self.figure.add_subplot(212) + ax2.bar(self.monthly_returns_x, self.monthly_returns_y) + ax2.set_title("Monthly PnL") + plt.setp(ax2.get_xticklabels(), rotation=90, ha="right", rotation_mode="anchor") + self.draw() diff --git a/src/nailab/ui/performancewidget.py b/src/nailab/ui/performancewidget.py new file mode 100644 index 0000000..6ca23ff --- /dev/null +++ b/src/nailab/ui/performancewidget.py @@ -0,0 +1,86 @@ + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.Qsci import * + +from ui_gen.performancewidget import Ui_PerformanceWidget + +from pathlib import PurePath + +import datetime +import json +import numpy as np + +class PerformanceWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.ui = Ui_PerformanceWidget() + self.ui.setupUi(self) + self.trades_series = {} + + def widget_type(self): + return "performance" + + def dailyToggled(self, v): + if v: + self.ui.correlationCanvas.setMode("daily") + + def monthlyToggled(self, v): + if v: + self.ui.correlationCanvas.setMode("monthly") + + def addResults(self): + filenames, _ = QtWidgets.QFileDialog.getOpenFileNames(self, self.tr("Select file to open"), "", self.tr("JSON files (*.json);;All Files (*)")) + results = [] + for filename in filenames: + with open(filename, "rt") as f: + name = PurePath(filename).name + QtWidgets.QListWidgetItem(name, self.ui.lw_strategies) + trades = json.loads(f.read()) + for trade in trades: + trade["entry_time"] = datetime.datetime.strptime(trade["entry_time"], "%Y-%m-%d %H:%M:%S") + trade["exit_time"] = datetime.datetime.strptime(trade["exit_time"], "%Y-%m-%d %H:%M:%S") + + self.trades_series[name] = trades + results.append({"name" : name, "trades" : trades}) + + self.ui.canvas.add_trades_series_batched(results) + self.ui.correlationCanvas.add_trades_series_batched(results) + self.update_stats() + + + def removeResults(self): + selected_rows = sorted(map(lambda x: self.ui.lw_strategies.row(x), self.ui.lw_strategies.selectedItems())) + for row in reversed(selected_rows): + item = self.ui.lw_strategies.takeItem(row) + self.ui.canvas.remove_trades_series(item.text()) + self.ui.correlationCanvas.remove_trades_series(item.text()) + + self.update_stats() + + def update_stats(self): + self.ui.tw_stats.clear() + monmean = np.mean(self.ui.canvas.monthly_returns_y) + monstd = np.std(self.ui.canvas.monthly_returns_y) + if monstd == 0: + monz = 0 + else: + monz = monmean / monstd + self.add_stat("Monthly mean", monmean) + self.add_stat("Monthly std. dev.", monstd) + self.add_stat("Z-Score", monz) + + self.ui.tw_stats.resizeColumnToContents(0) + self.ui.tw_stats.resizeColumnToContents(1) + + + def add_stat(self, name, value): + item = QtWidgets.QTreeWidgetItem(self.ui.tw_stats) + item.setText(0, name) + if isinstance(value, str): + item.setText(1, str) + elif isinstance(value, float): + item.setText(1, "{:.3f}".format(value)) + else: + item.setText(1, str(value)) + diff --git a/src/nailab/ui/strategywidget.py b/src/nailab/ui/strategywidget.py index c5fec0c..0ab0af7 100644 --- a/src/nailab/ui/strategywidget.py +++ b/src/nailab/ui/strategywidget.py @@ -94,6 +94,9 @@ class StrategyWidget(QtWidgets.QWidget): except Exception as e: print("Exception: ", e) + def widget_type(self): + return "strategy" + def is_saved(self): return self.saved @@ -191,12 +194,21 @@ class StrategyWidget(QtWidgets.QWidget): cumpnl = np.cumsum(pnl) drawdown = [] cur_max = 0 + drawdown_lengths = [] + cur_drawdown = 0 + cur_drawdown_max = 0 for i in range(0, len(cumpnl)): if cumpnl[i] > cur_max: cur_max = cumpnl[i] drawdown.append(0) + if cur_drawdown > 0: + drawdown_lengths.append((cur_drawdown, cur_drawdown_max)) + cur_drawdown = 0 + cur_drawdown_max = 0 else: drawdown.append(-(cur_max - cumpnl[i])) + cur_drawdown += 1 + cur_drawdown_max = max(cur_drawdown_max, cur_max - cumpnl[i]) if self.equity_widget is None: self.equity_widget = EquityChartWidget(self) self.ui.tabs.addTab(self.equity_widget, "Equity") @@ -204,6 +216,8 @@ class StrategyWidget(QtWidgets.QWidget): #self.equity_widget.set_data(self.result[2]) self.equity_widget.set_data(cumpnl, np.array(drawdown)) + print(drawdown_lengths) + def update_trades_list(self): if self.trades_widget is None: self.trades_widget = TradesListWidget(self) @@ -287,6 +301,18 @@ class StrategyWidget(QtWidgets.QWidget): profit_factor.setText(2, "{:.3f}".format(self.result[0]['short']['profit_factor'])) profit_factor.setText(3, "{:.3f}".format(self.result[0]['all']['profit_factor'])) + sharpe_ratio = QtWidgets.QTreeWidgetItem(self.result_widget) + sharpe_ratio.setText(0, "Sharpe ratio") + sharpe_ratio.setText(1, "{:.3f}".format(self.result[0]['long']['sharpe_ratio'])) + sharpe_ratio.setText(2, "{:.3f}".format(self.result[0]['short']['sharpe_ratio'])) + sharpe_ratio.setText(3, "{:.3f}".format(self.result[0]['all']['sharpe_ratio'])) + + t_stat = QtWidgets.QTreeWidgetItem(self.result_widget) + t_stat.setText(0, "t-statistics") + t_stat.setText(1, "{:.3f}".format(self.result[0]['long']['t_stat'])) + t_stat.setText(2, "{:.3f}".format(self.result[0]['short']['t_stat'])) + t_stat.setText(3, "{:.3f}".format(self.result[0]['all']['t_stat'])) + self.result_widget.resizeColumnToContents(0) def setError(self, errmsg): diff --git a/src/nailab/ui/tradeslistwidget.py b/src/nailab/ui/tradeslistwidget.py index e5e9a8d..5b9efec 100644 --- a/src/nailab/ui/tradeslistwidget.py +++ b/src/nailab/ui/tradeslistwidget.py @@ -4,6 +4,9 @@ from PyQt5.Qsci import * from ui_gen.tradeslistwidget import Ui_TradesListWidget +import json +import math + class TradesListWidget(QtWidgets.QWidget): def __init__(self, parent=None): @@ -43,3 +46,24 @@ class TradesListWidget(QtWidgets.QWidget): for i in range(0, 8): item.setBackground(i, brush) + def exportToFile(self): + filename, _ = QtWidgets.QFileDialog.getSaveFileName(None, self.tr("Select output file"), "", self.tr("JSON files (*.json);;All Files (*)")) + with open(filename, "wt") as f: + output = [] + for trade in self.trades: + if trade["is_long"]: + p = 100.0 * (trade["exit_price"] / trade["entry_price"] - 1) + log_ret = math.log(trade["exit_price"] / trade["entry_price"]) + else: + p = 100.0 * (trade["entry_price"] / trade["exit_price"] - 1) + log_ret = math.log(trade["entry_price"] / trade["exit_price"]) + t = { "pnl" : trade["pnl"], + "percentage" : p, + "log_return" : log_ret, + "entry_time" : trade["entry_time"].strftime("%Y-%m-%d %H:%M:%S"), + "exit_time" : trade["exit_time"].strftime("%Y-%m-%d %H:%M:%S") } + output.append(t) + f.write(json.dumps(output, indent=4, sort_keys=True)) + + + diff --git a/src/nailab/ui_gen/mainwindow.py b/src/nailab/ui_gen/mainwindow.py index c1bd557..5595ad3 100644 --- a/src/nailab/ui_gen/mainwindow.py +++ b/src/nailab/ui_gen/mainwindow.py @@ -29,6 +29,8 @@ class Ui_MainWindow(object): self.menuFile.setObjectName("menuFile") self.menuBacktest = QtWidgets.QMenu(self.menubar) self.menuBacktest.setObjectName("menuBacktest") + self.menuAnalysis = QtWidgets.QMenu(self.menubar) + self.menuAnalysis.setObjectName("menuAnalysis") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") @@ -62,19 +64,27 @@ class Ui_MainWindow(object): self.actionExit.setObjectName("actionExit") self.actionSave_as = QtWidgets.QAction(MainWindow) self.actionSave_as.setObjectName("actionSave_as") + self.actionPerformance = QtWidgets.QAction(MainWindow) + self.actionPerformance.setObjectName("actionPerformance") + self.actionExecute_in_Portfolio_Mode = QtWidgets.QAction(MainWindow) + self.actionExecute_in_Portfolio_Mode.setIcon(icon2) + self.actionExecute_in_Portfolio_Mode.setObjectName("actionExecute_in_Portfolio_Mode") self.menuFile.addAction(self.actionNew_strategy) self.menuFile.addAction(self.actionOpen_strategy) self.menuFile.addAction(self.actionSave_strategy) self.menuFile.addAction(self.actionSave_as) self.menuFile.addAction(self.actionExit) self.menuBacktest.addAction(self.actionExecute) + self.menuAnalysis.addAction(self.actionPerformance) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuBacktest.menuAction()) + self.menubar.addAction(self.menuAnalysis.menuAction()) self.toolBar.addAction(self.actionNew_strategy) self.toolBar.addAction(self.actionOpen_strategy) self.toolBar.addAction(self.actionSave_strategy) self.toolBar.addSeparator() self.toolBar.addAction(self.actionExecute) + self.toolBar.addAction(self.actionExecute_in_Portfolio_Mode) self.retranslateUi(MainWindow) self.tabs.setCurrentIndex(-1) @@ -84,6 +94,8 @@ class Ui_MainWindow(object): self.actionExecute.triggered.connect(MainWindow.executeStrategy) self.actionSave_strategy.triggered.connect(MainWindow.saveStrategy) self.actionSave_as.triggered.connect(MainWindow.saveStrategyAs) + self.actionPerformance.triggered.connect(MainWindow.performanceAnalysis) + self.actionExecute_in_Portfolio_Mode.triggered.connect(MainWindow.executeStrategyInPortfolioMode) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): @@ -91,6 +103,7 @@ class Ui_MainWindow(object): MainWindow.setWindowTitle(_translate("MainWindow", "Nailab")) self.menuFile.setTitle(_translate("MainWindow", "File")) self.menuBacktest.setTitle(_translate("MainWindow", "Backtest")) + self.menuAnalysis.setTitle(_translate("MainWindow", "Analysis")) self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar")) self.actionOpenTrades.setText(_translate("MainWindow", "Open...")) self.actionNew_strategy.setText(_translate("MainWindow", "New strategy")) @@ -102,5 +115,7 @@ class Ui_MainWindow(object): self.actionSave_strategy.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actionExit.setText(_translate("MainWindow", "Exit")) self.actionSave_as.setText(_translate("MainWindow", "Save as...")) + self.actionPerformance.setText(_translate("MainWindow", "Performance")) + self.actionExecute_in_Portfolio_Mode.setText(_translate("MainWindow", "Execute in Portfolio Mode")) import nailab_rc diff --git a/src/nailab/ui_gen/performancewidget.py b/src/nailab/ui_gen/performancewidget.py new file mode 100644 index 0000000..82d5ce4 --- /dev/null +++ b/src/nailab/ui_gen/performancewidget.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/performancewidget.ui' +# +# Created by: PyQt5 UI code generator 5.5.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_PerformanceWidget(object): + def setupUi(self, PerformanceWidget): + PerformanceWidget.setObjectName("PerformanceWidget") + PerformanceWidget.resize(780, 556) + self.gridLayout = QtWidgets.QGridLayout(PerformanceWidget) + self.gridLayout.setContentsMargins(2, 2, 2, 2) + self.gridLayout.setObjectName("gridLayout") + self.tab1 = QtWidgets.QTabWidget(PerformanceWidget) + self.tab1.setObjectName("tab1") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.gridLayout_2 = QtWidgets.QGridLayout(self.tab) + self.gridLayout_2.setContentsMargins(1, 1, 1, 1) + self.gridLayout_2.setObjectName("gridLayout_2") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setContentsMargins(4, 4, 4, 4) + self.verticalLayout.setObjectName("verticalLayout") + self.canvas = PerformanceCanvas(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.canvas.sizePolicy().hasHeightForWidth()) + self.canvas.setSizePolicy(sizePolicy) + self.canvas.setObjectName("canvas") + self.verticalLayout.addWidget(self.canvas) + self.tw_stats = QtWidgets.QTreeWidget(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tw_stats.sizePolicy().hasHeightForWidth()) + self.tw_stats.setSizePolicy(sizePolicy) + self.tw_stats.setObjectName("tw_stats") + self.tw_stats.header().setVisible(False) + self.verticalLayout.addWidget(self.tw_stats) + self.gridLayout_2.addLayout(self.verticalLayout, 0, 0, 1, 1) + self.tab1.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.rb_daily = QtWidgets.QRadioButton(self.tab_2) + self.rb_daily.setChecked(True) + self.rb_daily.setObjectName("rb_daily") + self.gridLayout_3.addWidget(self.rb_daily, 0, 0, 1, 1) + self.rb_monthly = QtWidgets.QRadioButton(self.tab_2) + self.rb_monthly.setObjectName("rb_monthly") + self.gridLayout_3.addWidget(self.rb_monthly, 0, 1, 1, 1) + self.correlationCanvas = CorrelationChartCanvas(self.tab_2) + self.correlationCanvas.setObjectName("correlationCanvas") + self.gridLayout_3.addWidget(self.correlationCanvas, 1, 0, 1, 2) + self.tab1.addTab(self.tab_2, "") + self.gridLayout.addWidget(self.tab1, 0, 2, 1, 1) + self.b_add = QtWidgets.QPushButton(PerformanceWidget) + self.b_add.setObjectName("b_add") + self.gridLayout.addWidget(self.b_add, 1, 0, 1, 1) + self.b_remove = QtWidgets.QPushButton(PerformanceWidget) + self.b_remove.setObjectName("b_remove") + self.gridLayout.addWidget(self.b_remove, 1, 1, 1, 1) + self.lw_strategies = QtWidgets.QListWidget(PerformanceWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lw_strategies.sizePolicy().hasHeightForWidth()) + self.lw_strategies.setSizePolicy(sizePolicy) + self.lw_strategies.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.lw_strategies.setObjectName("lw_strategies") + self.gridLayout.addWidget(self.lw_strategies, 0, 0, 1, 2) + + self.retranslateUi(PerformanceWidget) + self.tab1.setCurrentIndex(1) + self.b_add.clicked.connect(PerformanceWidget.addResults) + self.b_remove.clicked.connect(PerformanceWidget.removeResults) + self.rb_daily.toggled['bool'].connect(PerformanceWidget.dailyToggled) + self.rb_monthly.toggled['bool'].connect(PerformanceWidget.monthlyToggled) + QtCore.QMetaObject.connectSlotsByName(PerformanceWidget) + + def retranslateUi(self, PerformanceWidget): + _translate = QtCore.QCoreApplication.translate + PerformanceWidget.setWindowTitle(_translate("PerformanceWidget", "Form")) + self.tw_stats.headerItem().setText(0, _translate("PerformanceWidget", "1")) + self.tw_stats.headerItem().setText(1, _translate("PerformanceWidget", "2")) + self.tab1.setTabText(self.tab1.indexOf(self.tab), _translate("PerformanceWidget", "Returns and Drawdowns")) + self.rb_daily.setText(_translate("PerformanceWidget", "Daily")) + self.rb_monthly.setText(_translate("PerformanceWidget", "Monthly")) + self.tab1.setTabText(self.tab1.indexOf(self.tab_2), _translate("PerformanceWidget", "Correlations")) + self.b_add.setText(_translate("PerformanceWidget", "Add...")) + self.b_remove.setText(_translate("PerformanceWidget", "Remove")) + +from ui.correlationchartcanvas import CorrelationChartCanvas +from ui.performancecanvas import PerformanceCanvas diff --git a/src/nailab/ui_gen/tradeslistwidget.py b/src/nailab/ui_gen/tradeslistwidget.py index 387a2b5..2b93830 100644 --- a/src/nailab/ui_gen/tradeslistwidget.py +++ b/src/nailab/ui_gen/tradeslistwidget.py @@ -15,16 +15,23 @@ class Ui_TradesListWidget(object): self.gridLayout = QtWidgets.QGridLayout(TradesListWidget) self.gridLayout.setContentsMargins(1, 1, 1, 1) self.gridLayout.setObjectName("gridLayout") + self.b_exportToFile = QtWidgets.QPushButton(TradesListWidget) + self.b_exportToFile.setObjectName("b_exportToFile") + self.gridLayout.addWidget(self.b_exportToFile, 1, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 1, 1, 1, 1) self.trades = QtWidgets.QTreeWidget(TradesListWidget) self.trades.setObjectName("trades") - self.gridLayout.addWidget(self.trades, 0, 0, 1, 1) + self.gridLayout.addWidget(self.trades, 0, 0, 1, 2) self.retranslateUi(TradesListWidget) + self.b_exportToFile.clicked.connect(TradesListWidget.exportToFile) QtCore.QMetaObject.connectSlotsByName(TradesListWidget) def retranslateUi(self, TradesListWidget): _translate = QtCore.QCoreApplication.translate TradesListWidget.setWindowTitle(_translate("TradesListWidget", "Form")) + self.b_exportToFile.setText(_translate("TradesListWidget", "Export to file...")) self.trades.headerItem().setText(0, _translate("TradesListWidget", "D")) self.trades.headerItem().setText(1, _translate("TradesListWidget", "Amount")) self.trades.headerItem().setText(2, _translate("TradesListWidget", "Security")) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index afd60d0..550515f 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -64,8 +64,15 @@ + + + Analysis + + + + @@ -83,6 +90,7 @@ + @@ -144,6 +152,20 @@ Save as... + + + Performance + + + + + + :/main/icons/execute.png:/main/icons/execute.png + + + Execute in Portfolio Mode + + @@ -245,6 +267,38 @@ + + actionPerformance + triggered() + MainWindow + performanceAnalysis() + + + -1 + -1 + + + 529 + 293 + + + + + actionExecute_in_Portfolio_Mode + triggered() + MainWindow + executeStrategyInPortfolioMode() + + + -1 + -1 + + + 529 + 293 + + + openTrades() @@ -254,5 +308,7 @@ executeStrategy() saveStrategy() saveStrategyAs() + performanceAnalysis() + executeStrategyInPortfolioMode() diff --git a/ui/performancewidget.ui b/ui/performancewidget.ui new file mode 100644 index 0000000..8394d6e --- /dev/null +++ b/ui/performancewidget.ui @@ -0,0 +1,247 @@ + + + PerformanceWidget + + + + 0 + 0 + 780 + 556 + + + + Form + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 1 + + + + Returns and Drawdowns + + + + 1 + + + 1 + + + 1 + + + 1 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + false + + + + 1 + + + + + 2 + + + + + + + + + + + Correlations + + + + + + Daily + + + true + + + + + + + Monthly + + + + + + + + + + + + + + Add... + + + + + + + Remove + + + + + + + + 0 + 0 + + + + QAbstractItemView::ExtendedSelection + + + + + + + + PerformanceCanvas + QWidget +
ui/performancecanvas.h
+ 1 +
+ + CorrelationChartCanvas + QWidget +
ui/correlationchartcanvas.h
+ 1 +
+
+ + + + b_add + clicked() + PerformanceWidget + addResults() + + + 127 + 539 + + + 262 + 543 + + + + + b_remove + clicked() + PerformanceWidget + removeResults() + + + 239 + 539 + + + 372 + 542 + + + + + rb_daily + toggled(bool) + PerformanceWidget + dailyToggled(bool) + + + 314 + 44 + + + 508 + 537 + + + + + rb_monthly + toggled(bool) + PerformanceWidget + monthlyToggled(bool) + + + 572 + 47 + + + 647 + 546 + + + + + + addResults() + removeResults() + dailyToggled(bool) + monthlyToggled(bool) + +
diff --git a/ui/tradeslistwidget.ui b/ui/tradeslistwidget.ui index 22645eb..27f7935 100644 --- a/ui/tradeslistwidget.ui +++ b/ui/tradeslistwidget.ui @@ -26,7 +26,27 @@ 1 - + + + + Export to file... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -73,5 +93,25 @@ - + + + b_exportToFile + clicked() + TradesListWidget + exportToFile() + + + 78 + 550 + + + 101 + 551 + + + + + + exportToFile() +