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 @@
+
+
@@ -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
+
+ 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()
+