Browse Source

Performance analysis, portfolio mode

master
Denis Tereshkin 6 years ago
parent
commit
b41bf59aaf
  1. 1
      build_ui.sh
  2. 155
      src/nailab/execution/portfolioexecutor.py
  3. 154
      src/nailab/nailab_rc.py
  4. 138
      src/nailab/ui/correlationchartcanvas.py
  5. 30
      src/nailab/ui/mainwindow.py
  6. 92
      src/nailab/ui/performancecanvas.py
  7. 86
      src/nailab/ui/performancewidget.py
  8. 26
      src/nailab/ui/strategywidget.py
  9. 24
      src/nailab/ui/tradeslistwidget.py
  10. 15
      src/nailab/ui_gen/mainwindow.py
  11. 100
      src/nailab/ui_gen/performancewidget.py
  12. 9
      src/nailab/ui_gen/tradeslistwidget.py
  13. 56
      ui/mainwindow.ui
  14. 247
      ui/performancewidget.ui
  15. 44
      ui/tradeslistwidget.ui

1
build_ui.sh

@ -6,6 +6,7 @@ @@ -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

155
src/nailab/execution/portfolioexecutor.py

@ -0,0 +1,155 @@ @@ -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)

154
src/nailab/nailab_rc.py

@ -9,7 +9,7 @@ @@ -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"\ @@ -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"\ @@ -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"\ @@ -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"\ @@ -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():

138
src/nailab/ui/correlationchartcanvas.py

@ -0,0 +1,138 @@ @@ -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)

30
src/nailab/ui/mainwindow.py

@ -7,6 +7,7 @@ import sys @@ -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 * @@ -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): @@ -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): @@ -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)

92
src/nailab/ui/performancecanvas.py

@ -0,0 +1,92 @@ @@ -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()

86
src/nailab/ui/performancewidget.py

@ -0,0 +1,86 @@ @@ -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))

26
src/nailab/ui/strategywidget.py

@ -94,6 +94,9 @@ class StrategyWidget(QtWidgets.QWidget): @@ -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): @@ -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): @@ -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): @@ -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):

24
src/nailab/ui/tradeslistwidget.py

@ -4,6 +4,9 @@ from PyQt5.Qsci import * @@ -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): @@ -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))

15
src/nailab/ui_gen/mainwindow.py

@ -29,6 +29,8 @@ class Ui_MainWindow(object): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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

100
src/nailab/ui_gen/performancewidget.py

@ -0,0 +1,100 @@ @@ -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

9
src/nailab/ui_gen/tradeslistwidget.py

@ -15,16 +15,23 @@ class Ui_TradesListWidget(object): @@ -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"))

56
ui/mainwindow.ui

@ -64,8 +64,15 @@ @@ -64,8 +64,15 @@
</property>
<addaction name="actionExecute"/>
</widget>
<widget class="QMenu" name="menuAnalysis">
<property name="title">
<string>Analysis</string>
</property>
<addaction name="actionPerformance"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuBacktest"/>
<addaction name="menuAnalysis"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
@ -83,6 +90,7 @@ @@ -83,6 +90,7 @@
<addaction name="actionSave_strategy"/>
<addaction name="separator"/>
<addaction name="actionExecute"/>
<addaction name="actionExecute_in_Portfolio_Mode"/>
</widget>
<action name="actionOpenTrades">
<property name="text">
@ -144,6 +152,20 @@ @@ -144,6 +152,20 @@
<string>Save as...</string>
</property>
</action>
<action name="actionPerformance">
<property name="text">
<string>Performance</string>
</property>
</action>
<action name="actionExecute_in_Portfolio_Mode">
<property name="icon">
<iconset resource="../nailab.qrc">
<normaloff>:/main/icons/execute.png</normaloff>:/main/icons/execute.png</iconset>
</property>
<property name="text">
<string>Execute in Portfolio Mode</string>
</property>
</action>
</widget>
<resources>
<include location="../nailab.qrc"/>
@ -245,6 +267,38 @@ @@ -245,6 +267,38 @@
</hint>
</hints>
</connection>
<connection>
<sender>actionPerformance</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>performanceAnalysis()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>529</x>
<y>293</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionExecute_in_Portfolio_Mode</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>executeStrategyInPortfolioMode()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>529</x>
<y>293</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>openTrades()</slot>
@ -254,5 +308,7 @@ @@ -254,5 +308,7 @@
<slot>executeStrategy()</slot>
<slot>saveStrategy()</slot>
<slot>saveStrategyAs()</slot>
<slot>performanceAnalysis()</slot>
<slot>executeStrategyInPortfolioMode()</slot>
</slots>
</ui>

247
ui/performancewidget.ui

@ -0,0 +1,247 @@ @@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PerformanceWidget</class>
<widget class="QWidget" name="PerformanceWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>780</width>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item row="0" column="2">
<widget class="QTabWidget" name="tab1">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Returns and Drawdowns</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="PerformanceCanvas" name="canvas" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="tw_stats">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>1</string>
</property>
</column>
<column>
<property name="text">
<string>2</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Correlations</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QRadioButton" name="rb_daily">
<property name="text">
<string>Daily</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="rb_monthly">
<property name="text">
<string>Monthly</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="CorrelationChartCanvas" name="correlationCanvas" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="b_add">
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="b_remove">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QListWidget" name="lw_strategies">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PerformanceCanvas</class>
<extends>QWidget</extends>
<header>ui/performancecanvas.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>CorrelationChartCanvas</class>
<extends>QWidget</extends>
<header>ui/correlationchartcanvas.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>b_add</sender>
<signal>clicked()</signal>
<receiver>PerformanceWidget</receiver>
<slot>addResults()</slot>
<hints>
<hint type="sourcelabel">
<x>127</x>
<y>539</y>
</hint>
<hint type="destinationlabel">
<x>262</x>
<y>543</y>
</hint>
</hints>
</connection>
<connection>
<sender>b_remove</sender>
<signal>clicked()</signal>
<receiver>PerformanceWidget</receiver>
<slot>removeResults()</slot>
<hints>
<hint type="sourcelabel">
<x>239</x>
<y>539</y>
</hint>
<hint type="destinationlabel">
<x>372</x>
<y>542</y>
</hint>
</hints>
</connection>
<connection>
<sender>rb_daily</sender>
<signal>toggled(bool)</signal>
<receiver>PerformanceWidget</receiver>
<slot>dailyToggled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>314</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>508</x>
<y>537</y>
</hint>
</hints>
</connection>
<connection>
<sender>rb_monthly</sender>
<signal>toggled(bool)</signal>
<receiver>PerformanceWidget</receiver>
<slot>monthlyToggled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>572</x>
<y>47</y>
</hint>
<hint type="destinationlabel">
<x>647</x>
<y>546</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>addResults()</slot>
<slot>removeResults()</slot>
<slot>dailyToggled(bool)</slot>
<slot>monthlyToggled(bool)</slot>
</slots>
</ui>

44
ui/tradeslistwidget.ui

@ -26,7 +26,27 @@ @@ -26,7 +26,27 @@
<property name="bottomMargin">
<number>1</number>
</property>
<item row="0" column="0">
<item row="1" column="0">
<widget class="QPushButton" name="b_exportToFile">
<property name="text">
<string>Export to file...</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<widget class="QTreeWidget" name="trades">
<column>
<property name="text">
@ -73,5 +93,25 @@ @@ -73,5 +93,25 @@
</layout>
</widget>
<resources/>
<connections/>
<connections>
<connection>
<sender>b_exportToFile</sender>
<signal>clicked()</signal>
<receiver>TradesListWidget</receiver>
<slot>exportToFile()</slot>
<hints>
<hint type="sourcelabel">
<x>78</x>
<y>550</y>
</hint>
<hint type="destinationlabel">
<x>101</x>
<y>551</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>exportToFile()</slot>
</slots>
</ui>

Loading…
Cancel
Save