From fb364a5f37bf02c6898271401e678c2a2405e4d4 Mon Sep 17 00:00:00 2001 From: Denis Tereshkin Date: Tue, 13 Apr 2021 21:35:04 +0700 Subject: [PATCH] Some updates --- build_ui.sh | 20 +- src/nailab/data/datasourcemanager.py | 3 + src/nailab/data/yahoocsvfolderdatasource.py | 44 ++++ src/nailab/execution/executor.py | 10 + src/nailab/execution/portfolioexecutor.py | 27 +- src/nailab/nailab_rc.py | 249 ++++++++++-------- src/nailab/ui/mainwindow.py | 7 + src/nailab/ui/newdatasourcedialog.py | 8 +- src/nailab/ui/settingswindow.py | 26 ++ src/nailab/ui/statisticwidget.py | 117 ++++++++ src/nailab/ui/strategywidget.py | 28 +- .../ui_gen/csvdatasourceconfigwidget.py | 8 +- src/nailab/ui_gen/equitychartwidget.py | 8 +- src/nailab/ui_gen/mainwindow.py | 17 +- src/nailab/ui_gen/newdatasourcedialog.py | 10 +- src/nailab/ui_gen/performancewidget.py | 10 +- src/nailab/ui_gen/settingswindow.py | 51 ++++ src/nailab/ui_gen/statisticwidget.py | 62 +++++ src/nailab/ui_gen/strategywidget.py | 29 +- src/nailab/ui_gen/tradeslistwidget.py | 8 +- ui/mainwindow.ui | 27 +- ui/newdatasourcedialog.ui | 5 + ui/performancewidget.ui | 2 +- ui/settingswindow.ui | 105 ++++++++ ui/statisticwidget.ui | 113 ++++++++ ui/strategywidget.ui | 38 ++- 26 files changed, 848 insertions(+), 184 deletions(-) create mode 100644 src/nailab/data/yahoocsvfolderdatasource.py create mode 100644 src/nailab/ui/settingswindow.py create mode 100644 src/nailab/ui/statisticwidget.py create mode 100644 src/nailab/ui_gen/settingswindow.py create mode 100644 src/nailab/ui_gen/statisticwidget.py create mode 100644 ui/settingswindow.ui create mode 100644 ui/statisticwidget.ui diff --git a/build_ui.sh b/build_ui.sh index 2bf8775..de421d9 100755 --- a/build_ui.sh +++ b/build_ui.sh @@ -1,13 +1,15 @@ #!/bin/bash -/usr/bin/pyuic5 ui/csvdatasourceconfigwidget.ui > src/nailab/ui_gen/csvdatasourceconfigwidget.py -/usr/bin/pyuic5 ui/equitychartwidget.ui > src/nailab/ui_gen/equitychartwidget.py -/usr/bin/pyuic5 ui/mainwindow.ui > src/nailab/ui_gen/mainwindow.py -/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 +pyuic5 ui/csvdatasourceconfigwidget.ui > src/nailab/ui_gen/csvdatasourceconfigwidget.py +pyuic5 ui/equitychartwidget.ui > src/nailab/ui_gen/equitychartwidget.py +pyuic5 ui/mainwindow.ui > src/nailab/ui_gen/mainwindow.py +pyuic5 ui/newdatasourcedialog.ui > src/nailab/ui_gen/newdatasourcedialog.py +pyuic5 ui/strategywidget.ui > src/nailab/ui_gen/strategywidget.py +pyuic5 ui/tradeslistwidget.ui > src/nailab/ui_gen/tradeslistwidget.py +pyuic5 ui/performancewidget.ui > src/nailab/ui_gen/performancewidget.py +pyuic5 ui/settingswindow.ui > src/nailab/ui_gen/settingswindow.py +pyuic5 ui/statisticwidget.ui > src/nailab/ui_gen/statisticwidget.py + +pyrcc5 nailab.qrc -o src/nailab/nailab_rc.py diff --git a/src/nailab/data/datasourcemanager.py b/src/nailab/data/datasourcemanager.py index 6d4819f..fa93499 100644 --- a/src/nailab/data/datasourcemanager.py +++ b/src/nailab/data/datasourcemanager.py @@ -1,5 +1,6 @@ from data.csvfolderdatasource import CsvFolderDataSource +from data.yahoocsvfolderdatasource import YahooCsvFolderDataSource class DataSourceManager: @@ -19,6 +20,8 @@ class DataSourceManager: for s in serialized: if s[0] == "csv": self.add_source(CsvFolderDataSource.deserialize(s[1])) + if s[0] == "yahoo_csv": + self.add_source(YahooCsvFolderDataSource.deserialize(s[1])) def add_source(self, source): self.sources.append(source) diff --git a/src/nailab/data/yahoocsvfolderdatasource.py b/src/nailab/data/yahoocsvfolderdatasource.py new file mode 100644 index 0000000..60b3e40 --- /dev/null +++ b/src/nailab/data/yahoocsvfolderdatasource.py @@ -0,0 +1,44 @@ + +from data.datasource import DataSource + +from naiback.data.feeds.yahoofeed import YahooCSVFeed + +import glob +import os + +class YahooCsvFolderDataSource(DataSource): + + def __init__(self, name, path): + super().__init__(name) + + self.type = "yahoo_csv" + + self.path = path + self.feeds = [] + self._discover_feeds() + + @classmethod + def deserialize(self, data): + return YahooCsvFolderDataSource(data[0], data[1]) + + def serialize(self): + return (self.name, self.path) + + def available_feeds(self): + return [f[1] for f in self.feeds] + + def refresh(self): + self._discover_feeds() + + def get_feed(self, feed_id): + for path, f_id in self.feeds: + if f_id == feed_id: + with open(path, 'r') as f: + return YahooCSVFeed(f) + raise NailabError('Unable to obtain feed: {}'.format(feed_id)) + + + def _discover_feeds(self): + files = glob.glob(self.path + '/*.csv') + self.feeds = [(f, os.path.basename(f)) for f in files] + diff --git a/src/nailab/execution/executor.py b/src/nailab/execution/executor.py index 36079d2..990996d 100644 --- a/src/nailab/execution/executor.py +++ b/src/nailab/execution/executor.py @@ -3,6 +3,7 @@ import sys import importlib from importlib.machinery import SourceFileLoader import inspect +from PyQt5 import QtCore from naiback.strategy import Strategy @@ -23,6 +24,9 @@ class Executor: for feed in feeds: strategy.add_feed(feed) + (commission_percentage, commission_per_share) = self.load_commissions() + + strategy.broker.set_commission(commission_percentage, commission_per_share) if extents is None: strategy.run() else: @@ -34,3 +38,9 @@ class Executor: return (results, trades, equity) + + def load_commissions(self): + s = QtCore.QSettings() + + return (float(s.value("commission/percentage", 0)), float(s.value("commission/per_share", 0))) + diff --git a/src/nailab/execution/portfolioexecutor.py b/src/nailab/execution/portfolioexecutor.py index 7f7ceab..b2b6055 100644 --- a/src/nailab/execution/portfolioexecutor.py +++ b/src/nailab/execution/portfolioexecutor.py @@ -54,18 +54,28 @@ class PortfolioExecutor: all_trades = list(sorted(trades1 + trades2, key=lambda x: x['entry_time'])) result = [] current_trades = [] + # TODO customize + size_per_position = 1000000 + cash = 1000000 max_trades = 3 for trade in all_trades: if len(current_trades) < max_trades: + size = math.floor(size_per_position / (trade['entry_price'] * max_trades)) + cash -= size * trade['entry_price'] + trade['size'] = size + if trade['is_long']: + trade['pnl'] = size * (trade['exit_price'] - trade['entry_price']) + else: + trade['pnl'] = -size * (trade['exit_price'] - trade['entry_price']) 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 + cash += ct['size'] * ct['exit_price'] current_trades = new_current_trades return result @@ -90,6 +100,10 @@ class PortfolioExecutor: result['long']['number_of_trades'] = len(longs) result['short']['number_of_trades'] = len(shorts) + result['all']['total_commission'] = 0 # TODO + result['long']['total_commission'] = 0 + result['short']['total_commission'] = 0 + 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']) @@ -126,22 +140,25 @@ class PortfolioExecutor: 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']['z_score'] = sharpe result['all']['t_stat'] = tstat + result['all']['kelly'] = 0 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']['z_score'] = sharpe result['long']['t_stat'] = tstat + result['long']['kelly'] = 0 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']['z_score'] = sharpe result['short']['t_stat'] = tstat + result['short']['kelly'] = 0 return result diff --git a/src/nailab/nailab_rc.py b/src/nailab/nailab_rc.py index 7ca94cf..c7fb1af 100644 --- a/src/nailab/nailab_rc.py +++ b/src/nailab/nailab_rc.py @@ -2,13 +2,110 @@ # Resource object code # -# Created by: The Resource Compiler for PyQt5 (Qt v5.5.1) +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.1) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ +\x00\x00\x03\x8f\ +\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\x03\x0c\x49\x44\ +\x41\x54\x68\x81\xed\x98\x3b\x6f\x13\x41\x14\x85\xcf\xdd\x75\x22\ +\x2b\x7e\xc4\x8b\x82\xac\x74\x48\x3c\x44\x8f\x68\x20\x11\x54\x54\ +\x54\x48\x20\x90\x22\x85\x9a\x74\x94\xbc\xc4\xa3\x83\x48\xf4\x54\ +\x49\x41\x89\x8c\x22\x7e\x40\xc2\x8f\x48\x49\x81\x14\x51\x61\x93\ +\x38\xb1\xe3\xd7\x5e\x0a\x6c\x65\xbc\x33\x76\x76\x66\xef\xda\x29\ +\xf8\x24\x37\x33\x9e\xdd\x73\x76\xce\x1d\xdf\x35\x31\x33\x6c\xa9\ +\x57\x82\x55\xaa\x7a\x1b\xda\x44\x96\x11\x5e\x08\xad\xaf\x07\xf0\ +\x66\xf1\xe6\xfe\x63\x87\x85\xf0\x5c\x16\xa1\x45\x2f\x8c\x32\x02\ +\xfb\x87\x01\xd0\xee\x51\x36\xff\xc4\x49\x07\x1c\x0c\x1c\x7c\x9b\ +\xbf\x4c\x0d\xba\xa4\x4d\xf8\x00\x17\xad\x0d\x34\xbc\xb0\x7b\x7f\ +\xf1\xda\x5e\xc3\x76\xe1\x00\x6b\x03\x7e\xd3\xff\x08\x83\x4e\x9e\ +\x67\x80\xec\xae\x45\x44\x6b\xf9\xe5\xfa\xae\xad\x06\x15\x3b\x03\ +\xdb\x94\xe1\x26\xee\x98\xa6\x38\xb0\xcd\x3e\x6f\x16\x6e\xd4\x36\ +\x2c\x17\x69\x58\x19\x38\xa8\x96\x9e\xa3\x43\x33\x9a\x94\x3c\x03\ +\xda\xe8\x38\x92\xe5\x5e\xc5\xca\x00\x1d\xd3\x9a\x71\xa2\x64\x95\ +\xfd\xc4\xb9\x57\x89\x6d\xe0\xb0\x12\xdc\xa2\x26\x9d\xd7\x26\x66\ +\xfa\x3b\x10\x13\x89\xdc\xab\xc4\x36\xc0\x1d\xbc\x37\x8e\x5b\x65\ +\x5f\x26\xf7\x2a\xb1\x0c\xfc\xf9\x1a\x94\xa8\xe1\x5d\xd7\x26\xa8\ +\x7f\xfa\xc4\x42\x2e\xf7\x2a\xb1\x0c\xf8\x5d\x7c\x40\x4f\x3f\x24\ +\xb9\xc8\x80\x1f\xeb\x3e\xa2\xb9\x57\x89\x17\xa1\x63\xef\x91\x69\ +\x38\xee\x2f\xaf\x74\xee\x55\x4e\x35\x50\xaf\x04\xab\xd4\x42\x4e\ +\x9b\xc8\xf2\xbf\xcf\xa9\xc8\xe7\x5e\xe5\xf4\x1d\x48\xd4\xf7\xa4\ +\x93\x7b\x95\xb1\x06\x12\xf6\x3d\xa9\xe5\x5e\x65\xac\x81\x24\x7d\ +\x4f\x9a\xb9\x1f\xba\xcf\xc8\xf7\x81\x6d\xca\x1c\xfe\x0c\x1a\xa6\ +\xd6\x21\xbc\xd8\xb3\x6c\x1d\x1c\x61\x7a\x53\x5c\xaa\xbd\x1e\xf7\ +\x95\x91\x3b\x20\xd7\xf7\x38\x12\x43\x3c\x30\xc6\x80\x50\xdf\xe3\ +\x46\x4c\xf1\xc0\x08\x03\x52\x7d\x8f\x13\x16\xe2\x81\x11\x06\x64\ +\xfa\x1e\x07\x2c\xc5\x03\x06\x03\x32\x7d\x8f\x03\x0e\xe2\x01\x83\ +\x01\xbf\xe7\xad\x27\xec\x7b\xec\x71\x14\x0f\x98\x22\xd4\xc4\x43\ +\xe3\x3d\x9c\xfe\x71\x88\x41\x02\xf1\xc0\xb8\xdf\x01\x07\xaa\x9f\ +\x16\x5f\xb6\xf6\xe6\xde\x46\xc7\xfd\xb9\x2e\xb2\x4b\xfb\xfa\x82\ +\x84\xe2\x01\xd7\xff\x85\x46\x10\x1e\x66\x9e\x9a\xc6\x67\xae\x18\ +\xba\x09\x01\xf1\x80\xe0\x0e\x1c\x6d\x95\xcb\xfc\xbb\xfb\x0b\xe1\ +\x70\xfd\x70\xc0\xe0\x72\xe4\xf4\x12\x12\x0f\x08\xee\x40\xd8\xee\ +\xac\x47\xc5\xc3\x07\x78\x21\x3d\xf1\x80\xa0\x01\x6a\xd2\xbd\xe8\ +\x18\x2f\x84\xc3\x27\x97\xb0\x78\x40\xc8\xc0\xc1\x97\x73\x2b\x68\ +\x63\x6e\x68\x70\x16\x60\xb5\xed\x48\x41\x3c\x20\x64\xc0\xeb\xe2\ +\x59\x74\x8c\xcb\xe1\x49\xcb\x9d\x92\x78\x40\xc0\xc0\xd1\x56\xb9\ +\x8c\x06\x5d\x55\xc7\x38\xc7\xe0\x5c\xff\xe9\xa7\x28\x1e\x10\x30\ +\xa0\x15\x2f\xe1\xe4\xd4\x49\x59\x3c\x20\x60\x20\x5a\xbc\x5c\x62\ +\x60\x16\x13\x11\x0f\x24\x34\xa0\x15\xef\xe0\xd8\x9c\x90\x78\x20\ +\xa1\x81\x68\xf1\xf2\x42\x08\x78\x93\x13\x0f\x24\x30\xa0\x15\xef\ +\x2c\xc0\xf3\x78\x37\x49\xf1\x40\x02\x03\x5a\xf1\x06\xfc\xb9\xb8\ +\x5c\x7b\x25\xa2\xca\x02\x67\x03\x6a\xf1\x72\x81\x77\xf2\x77\xab\ +\x2b\x32\x92\xec\x70\x32\xa0\x16\x2f\x17\x78\xa7\xf0\xa0\x7a\x5b\ +\x54\x95\x05\x4e\x06\x06\xc5\x3b\x6d\xf1\x80\x43\x3b\x3d\x68\x9b\ +\x39\xc7\xdf\xa7\x2d\x1e\x00\x32\xb6\x0b\xc2\x76\x67\x1d\x39\x9c\ +\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\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\x02\x0e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -87,103 +184,6 @@ 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\ -\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\x03\x0c\x49\x44\ -\x41\x54\x68\x81\xed\x98\x3b\x6f\x13\x41\x14\x85\xcf\xdd\x75\x22\ -\x2b\x7e\xc4\x8b\x82\xac\x74\x48\x3c\x44\x8f\x68\x20\x11\x54\x54\ -\x54\x48\x20\x90\x22\x85\x9a\x74\x94\xbc\xc4\xa3\x83\x48\xf4\x54\ -\x49\x41\x89\x8c\x22\x7e\x40\xc2\x8f\x48\x49\x81\x14\x51\x61\x93\ -\x38\xb1\xe3\xd7\x5e\x0a\x6c\x65\xbc\x33\x76\x76\x66\xef\xda\x29\ -\xf8\x24\x37\x33\x9e\xdd\x73\x76\xce\x1d\xdf\x35\x31\x33\x6c\xa9\ -\x57\x82\x55\xaa\x7a\x1b\xda\x44\x96\x11\x5e\x08\xad\xaf\x07\xf0\ -\x66\xf1\xe6\xfe\x63\x87\x85\xf0\x5c\x16\xa1\x45\x2f\x8c\x32\x02\ -\xfb\x87\x01\xd0\xee\x51\x36\xff\xc4\x49\x07\x1c\x0c\x1c\x7c\x9b\ -\xbf\x4c\x0d\xba\xa4\x4d\xf8\x00\x17\xad\x0d\x34\xbc\xb0\x7b\x7f\ -\xf1\xda\x5e\xc3\x76\xe1\x00\x6b\x03\x7e\xd3\xff\x08\x83\x4e\x9e\ -\x67\x80\xec\xae\x45\x44\x6b\xf9\xe5\xfa\xae\xad\x06\x15\x3b\x03\ -\xdb\x94\xe1\x26\xee\x98\xa6\x38\xb0\xcd\x3e\x6f\x16\x6e\xd4\x36\ -\x2c\x17\x69\x58\x19\x38\xa8\x96\x9e\xa3\x43\x33\x9a\x94\x3c\x03\ -\xda\xe8\x38\x92\xe5\x5e\xc5\xca\x00\x1d\xd3\x9a\x71\xa2\x64\x95\ -\xfd\xc4\xb9\x57\x89\x6d\xe0\xb0\x12\xdc\xa2\x26\x9d\xd7\x26\x66\ -\xfa\x3b\x10\x13\x89\xdc\xab\xc4\x36\xc0\x1d\xbc\x37\x8e\x5b\x65\ -\x5f\x26\xf7\x2a\xb1\x0c\xfc\xf9\x1a\x94\xa8\xe1\x5d\xd7\x26\xa8\ -\x7f\xfa\xc4\x42\x2e\xf7\x2a\xb1\x0c\xf8\x5d\x7c\x40\x4f\x3f\x24\ -\xb9\xc8\x80\x1f\xeb\x3e\xa2\xb9\x57\x89\x17\xa1\x63\xef\x91\x69\ -\x38\xee\x2f\xaf\x74\xee\x55\x4e\x35\x50\xaf\x04\xab\xd4\x42\x4e\ -\x9b\xc8\xf2\xbf\xcf\xa9\xc8\xe7\x5e\xe5\xf4\x1d\x48\xd4\xf7\xa4\ -\x93\x7b\x95\xb1\x06\x12\xf6\x3d\xa9\xe5\x5e\x65\xac\x81\x24\x7d\ -\x4f\x9a\xb9\x1f\xba\xcf\xc8\xf7\x81\x6d\xca\x1c\xfe\x0c\x1a\xa6\ -\xd6\x21\xbc\xd8\xb3\x6c\x1d\x1c\x61\x7a\x53\x5c\xaa\xbd\x1e\xf7\ -\x95\x91\x3b\x20\xd7\xf7\x38\x12\x43\x3c\x30\xc6\x80\x50\xdf\xe3\ -\x46\x4c\xf1\xc0\x08\x03\x52\x7d\x8f\x13\x16\xe2\x81\x11\x06\x64\ -\xfa\x1e\x07\x2c\xc5\x03\x06\x03\x32\x7d\x8f\x03\x0e\xe2\x01\x83\ -\x01\xbf\xe7\xad\x27\xec\x7b\xec\x71\x14\x0f\x98\x22\xd4\xc4\x43\ -\xe3\x3d\x9c\xfe\x71\x88\x41\x02\xf1\xc0\xb8\xdf\x01\x07\xaa\x9f\ -\x16\x5f\xb6\xf6\xe6\xde\x46\xc7\xfd\xb9\x2e\xb2\x4b\xfb\xfa\x82\ -\x84\xe2\x01\xd7\xff\x85\x46\x10\x1e\x66\x9e\x9a\xc6\x67\xae\x18\ -\xba\x09\x01\xf1\x80\xe0\x0e\x1c\x6d\x95\xcb\xfc\xbb\xfb\x0b\xe1\ -\x70\xfd\x70\xc0\xe0\x72\xe4\xf4\x12\x12\x0f\x08\xee\x40\xd8\xee\ -\xac\x47\xc5\xc3\x07\x78\x21\x3d\xf1\x80\xa0\x01\x6a\xd2\xbd\xe8\ -\x18\x2f\x84\xc3\x27\x97\xb0\x78\x40\xc8\xc0\xc1\x97\x73\x2b\x68\ -\x63\x6e\x68\x70\x16\x60\xb5\xed\x48\x41\x3c\x20\x64\xc0\xeb\xe2\ -\x59\x74\x8c\xcb\xe1\x49\xcb\x9d\x92\x78\x40\xc0\xc0\xd1\x56\xb9\ -\x8c\x06\x5d\x55\xc7\x38\xc7\xe0\x5c\xff\xe9\xa7\x28\x1e\x10\x30\ -\xa0\x15\x2f\xe1\xe4\xd4\x49\x59\x3c\x20\x60\x20\x5a\xbc\x5c\x62\ -\x60\x16\x13\x11\x0f\x24\x34\xa0\x15\xef\xe0\xd8\x9c\x90\x78\x20\ -\xa1\x81\x68\xf1\xf2\x42\x08\x78\x93\x13\x0f\x24\x30\xa0\x15\xef\ -\x2c\xc0\xf3\x78\x37\x49\xf1\x40\x02\x03\x5a\xf1\x06\xfc\xb9\xb8\ -\x5c\x7b\x25\xa2\xca\x02\x67\x03\x6a\xf1\x72\x81\x77\xf2\x77\xab\ -\x2b\x32\x92\xec\x70\x32\xa0\x16\x2f\x17\x78\xa7\xf0\xa0\x7a\x5b\ -\x54\x95\x05\x4e\x06\x06\xc5\x3b\x6d\xf1\x80\x43\x3b\x3d\x68\x9b\ -\x39\xc7\xdf\xa7\x2d\x1e\x00\x32\xb6\x0b\xc2\x76\x67\x1d\x39\x9c\ -\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\ " qt_resource_name = b"\ @@ -195,6 +195,14 @@ qt_resource_name = b"\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ +\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\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\ @@ -203,30 +211,47 @@ qt_resource_name = b"\ \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\ " -qt_resource_struct = b"\ +qt_resource_struct_v1 = 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\x50\x00\x00\x00\x00\x00\x01\x00\x00\x05\xca\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x07\xdc\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x93\ \x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\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\ " +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x50\x00\x00\x00\x00\x00\x01\x00\x00\x05\xca\ +\x00\x00\x01\x47\xc9\x98\x5f\x00\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x07\xdc\ +\x00\x00\x01\x47\xc9\x98\x5f\x00\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x93\ +\x00\x00\x01\x47\xc9\x98\x5f\x00\ +\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x47\xc9\x98\x5f\x00\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/src/nailab/ui/mainwindow.py b/src/nailab/ui/mainwindow.py index d2fabfb..33591c8 100644 --- a/src/nailab/ui/mainwindow.py +++ b/src/nailab/ui/mainwindow.py @@ -16,6 +16,7 @@ from PyQt5.Qsci import * from ui_gen.mainwindow import Ui_MainWindow from ui.strategywidget import StrategyWidget from ui.performancewidget import PerformanceWidget +from ui.settingswindow import SettingsWindow from naiback.strategy import Strategy from templates.new_strategy import new_strategy_template @@ -117,6 +118,12 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.tabs.widget(tab_index).save_state() self.ui.tabs.removeTab(tab_index) + def showSettingsDialog(self): + dlg = SettingsWindow(self) + dlg.exec() + if dlg.result() == QtWidgets.QDialog.Accepted: + dlg.saveSettings() + def performanceAnalysis(self): self.ui.tabs.addTab(PerformanceWidget(), self.tr("Performance analysis")) diff --git a/src/nailab/ui/newdatasourcedialog.py b/src/nailab/ui/newdatasourcedialog.py index d55ad2e..ec54d35 100644 --- a/src/nailab/ui/newdatasourcedialog.py +++ b/src/nailab/ui/newdatasourcedialog.py @@ -6,6 +6,7 @@ from ui_gen.newdatasourcedialog import Ui_NewDataSourceDialog from ui.csvdatasourceconfigwidget import CSVDataSourceConfigWidget from data.csvfolderdatasource import CsvFolderDataSource +from data.yahoocsvfolderdatasource import YahooCsvFolderDataSource class NewDataSourceDialog(QtWidgets.QDialog): @@ -19,6 +20,7 @@ class NewDataSourceDialog(QtWidgets.QDialog): self.ui.widgets.setCurrentIndex(index) def get_data_source(self): - return CsvFolderDataSource(self.ui.e_sourceName.text(), self.ui.widgets.currentWidget().ui.e_path.text()) - - + if self.ui.cb_sourceType.currentText() == "CSV": + return CsvFolderDataSource(self.ui.e_sourceName.text(), self.ui.widgets.currentWidget().ui.e_path.text()) + else: + return YahooCsvFolderDataSource(self.ui.e_sourceName.text(), self.ui.widgets.currentWidget().ui.e_path.text()) diff --git a/src/nailab/ui/settingswindow.py b/src/nailab/ui/settingswindow.py new file mode 100644 index 0000000..d2bc90e --- /dev/null +++ b/src/nailab/ui/settingswindow.py @@ -0,0 +1,26 @@ + +from ui_gen.settingswindow import Ui_SettingsWindow +from PyQt5 import QtCore, QtGui, QtWidgets + +class SettingsWindow(QtWidgets.QDialog): + + def __init__(self, parent=None): + super().__init__(parent) + + self.ui = Ui_SettingsWindow() + self.ui.setupUi(self) + + self.loadSettings() + + + def loadSettings(self): + s = QtCore.QSettings() + + self.ui.sb_commissionPercentage.setValue(float(s.value("commission/percentage", 0))); + self.ui.sb_commissionPerShare.setValue(float(s.value("commission/per_share", 0))) + + def saveSettings(self): + s = QtCore.QSettings() + + s.setValue("commission/percentage", float(self.ui.sb_commissionPercentage.value())) + s.setValue("commission/per_share", float(self.ui.sb_commissionPerShare.value())) diff --git a/src/nailab/ui/statisticwidget.py b/src/nailab/ui/statisticwidget.py new file mode 100644 index 0000000..5a988ac --- /dev/null +++ b/src/nailab/ui/statisticwidget.py @@ -0,0 +1,117 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.Qsci import * + +from ui_gen.statisticwidget import Ui_StatisticWidget + +import math + +def calc_ratio(a, b): + if b != 0: + return a / b + else: + return 0 + +class StatisticWidget(QtWidgets.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.ui = Ui_StatisticWidget() + self.ui.setupUi(self) + + self.trades = [] + + + def set_trades(self, trades): + self.trades = trades + + self.update_statistic() + + def update_statistic(self): + + self.update_by_ticker() + self.update_seasonality() + + def update_by_ticker(self): + self.ui.tw_byTicker.clear() + by_ticker = {} + for trade in self.trades: + try: + by_ticker[trade["security"]].append(trade) + except KeyError: + by_ticker[trade["security"]] = [trade] + + for ticker, trades in by_ticker.items(): + item = QtWidgets.QTreeWidgetItem(self.ui.tw_byTicker) + stats = self.calc_stats(trades) + item.setText(0, ticker) + item.setText(1, "{:.4f}".format(stats["net_profit"])) + item.setText(2, "{}".format(stats["total_trades"])) + item.setText(3, "{:.2f}%".format(stats["avg_percentage"])) + + + def calc_stats(self, trades): + net_profit = 0 + sum_percentage = 0 + for trade in trades: + sum_percentage += trade["profit_percentage"] + net_profit += trade["pnl"] + + return { "net_profit" : net_profit, + "total_trades" : len(trades), + "avg_percentage" : calc_ratio(sum_percentage, len(trades)) } + + + def update_seasonality(self ): + self.ui.tw_seasonality.clear() + + by_weekday = {} + by_monthday = {} + by_month = {} + + for trade in self.trades: + try: + by_weekday[trade["entry_time"].date().isoweekday()].append(trade) + except KeyError: + by_weekday[trade["entry_time"].date().isoweekday()] = [trade] + + try: + by_monthday[trade["entry_time"].date().day].append(trade) + except KeyError: + by_monthday[trade["entry_time"].date().day] = [trade] + + try: + by_month[trade["entry_time"].date().month].append(trade) + except KeyError: + by_month[trade["entry_time"].date().month] = [trade] + + item_by_weekday = QtWidgets.QTreeWidgetItem(self.ui.tw_seasonality) + item_by_weekday.setText(0, "By weekday") + for weekday, trades in sorted(by_weekday.items()): + item = QtWidgets.QTreeWidgetItem(item_by_weekday) + stats = self.calc_stats(trades) + item.setText(0, "{}".format(weekday)) + item.setText(1, "{:.4f}".format(stats["net_profit"])) + item.setText(2, "{}".format(stats["total_trades"])) + item.setText(3, "{:.2f}%".format(stats["avg_percentage"])) + + item_by_monthday = QtWidgets.QTreeWidgetItem(self.ui.tw_seasonality) + item_by_monthday.setText(0, "By day-of-month") + for monthday, trades in sorted(by_monthday.items()): + item = QtWidgets.QTreeWidgetItem(item_by_monthday) + stats = self.calc_stats(trades) + item.setText(0, "{:02d}".format(monthday)) + item.setText(1, "{:.4f}".format(stats["net_profit"])) + item.setText(2, "{}".format(stats["total_trades"])) + item.setText(3, "{:.2f}%".format(stats["avg_percentage"])) + + item_by_month = QtWidgets.QTreeWidgetItem(self.ui.tw_seasonality) + item_by_month.setText(0, "By month") + for month, trades in sorted(by_month.items()): + item = QtWidgets.QTreeWidgetItem(item_by_month) + stats = self.calc_stats(trades) + item.setText(0, "{:02d}".format(month)) + item.setText(1, "{:.4f}".format(stats["net_profit"])) + item.setText(2, "{}".format(stats["total_trades"])) + item.setText(3, "{:.2f}%".format(stats["avg_percentage"])) + diff --git a/src/nailab/ui/strategywidget.py b/src/nailab/ui/strategywidget.py index 0ab0af7..1d30f19 100644 --- a/src/nailab/ui/strategywidget.py +++ b/src/nailab/ui/strategywidget.py @@ -11,6 +11,7 @@ from ui_gen.strategywidget import Ui_StrategyWidget from ui.newdatasourcedialog import NewDataSourceDialog from ui.equitychartwidget import EquityChartWidget from ui.tradeslistwidget import TradesListWidget +from ui.statisticwidget import StatisticWidget from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -76,6 +77,7 @@ class StrategyWidget(QtWidgets.QWidget): self.result_widget = None self.equity_widget = None self.trades_widget = None + self.statistic_widget = None self.watchdog_handler = FileModifiedHandler(self.file_modified) self.watchdog = None @@ -223,8 +225,12 @@ class StrategyWidget(QtWidgets.QWidget): self.trades_widget = TradesListWidget(self) self.ui.tabs.addTab(self.trades_widget, "Trades") + if self.statistic_widget is None: + self.statistic_widget = StatisticWidget(self) + self.ui.tabs.addTab(self.statistic_widget, "Statistic") + self.trades_widget.set_trades(self.result[1]) - + self.statistic_widget.set_trades(self.result[1]) def update_result(self): if self.result_widget is None: @@ -265,6 +271,12 @@ class StrategyWidget(QtWidgets.QWidget): net_profit.setText(2, "{:.3f}".format(self.result[0]['short']['net_profit'])) net_profit.setText(3, "{:.3f}".format(self.result[0]['all']['net_profit'])) + total_commission = QtWidgets.QTreeWidgetItem(self.result_widget) + total_commission.setText(0, "Total commission") + total_commission.setText(1, "{:.3f}".format(self.result[0]['long']['total_commission'])) + total_commission.setText(2, "{:.3f}".format(self.result[0]['short']['total_commission'])) + total_commission.setText(3, "{:.3f}".format(self.result[0]['all']['total_commission'])) + total_won = QtWidgets.QTreeWidgetItem(self.result_widget) total_won.setText(0, "Total won") total_won.setText(1, "{:.3f}".format(self.result[0]['long']['total_won'])) @@ -302,10 +314,10 @@ class StrategyWidget(QtWidgets.QWidget): 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'])) + sharpe_ratio.setText(0, "Z-score") + sharpe_ratio.setText(1, "{:.3f}".format(self.result[0]['long']['z_score'])) + sharpe_ratio.setText(2, "{:.3f}".format(self.result[0]['short']['z_score'])) + sharpe_ratio.setText(3, "{:.3f}".format(self.result[0]['all']['z_score'])) t_stat = QtWidgets.QTreeWidgetItem(self.result_widget) t_stat.setText(0, "t-statistics") @@ -313,6 +325,12 @@ class StrategyWidget(QtWidgets.QWidget): t_stat.setText(2, "{:.3f}".format(self.result[0]['short']['t_stat'])) t_stat.setText(3, "{:.3f}".format(self.result[0]['all']['t_stat'])) + kelly = QtWidgets.QTreeWidgetItem(self.result_widget) + kelly.setText(0, "Kelly ratio") + kelly.setText(1, "{:.1f}%".format(100. * self.result[0]['long']['kelly'])) + kelly.setText(2, "{:.1f}%".format(100. * self.result[0]['short']['kelly'])) + kelly.setText(3, "{:.1f}%".format(100. * self.result[0]['all']['kelly'])) + self.result_widget.resizeColumnToContents(0) def setError(self, errmsg): diff --git a/src/nailab/ui_gen/csvdatasourceconfigwidget.py b/src/nailab/ui_gen/csvdatasourceconfigwidget.py index 940fac6..8a3626d 100644 --- a/src/nailab/ui_gen/csvdatasourceconfigwidget.py +++ b/src/nailab/ui_gen/csvdatasourceconfigwidget.py @@ -2,12 +2,15 @@ # Form implementation generated from reading ui file 'ui/csvdatasourceconfigwidget.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_CSVDataSourceConfigWidget(object): def setupUi(self, CSVDataSourceConfigWidget): CSVDataSourceConfigWidget.setObjectName("CSVDataSourceConfigWidget") @@ -45,4 +48,3 @@ class Ui_CSVDataSourceConfigWidget(object): CSVDataSourceConfigWidget.setWindowTitle(_translate("CSVDataSourceConfigWidget", "Form")) self.label.setText(_translate("CSVDataSourceConfigWidget", "CSV folder path")) self.b_browse.setText(_translate("CSVDataSourceConfigWidget", "...")) - diff --git a/src/nailab/ui_gen/equitychartwidget.py b/src/nailab/ui_gen/equitychartwidget.py index d5ebd13..26b638c 100644 --- a/src/nailab/ui_gen/equitychartwidget.py +++ b/src/nailab/ui_gen/equitychartwidget.py @@ -2,12 +2,15 @@ # Form implementation generated from reading ui file 'ui/equitychartwidget.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_EquityChartWidget(object): def setupUi(self, EquityChartWidget): EquityChartWidget.setObjectName("EquityChartWidget") @@ -24,5 +27,4 @@ class Ui_EquityChartWidget(object): def retranslateUi(self, EquityChartWidget): _translate = QtCore.QCoreApplication.translate EquityChartWidget.setWindowTitle(_translate("EquityChartWidget", "Form")) - from ui.equitychartcanvas import EquityChartCanvas diff --git a/src/nailab/ui_gen/mainwindow.py b/src/nailab/ui_gen/mainwindow.py index 5595ad3..a7fed67 100644 --- a/src/nailab/ui_gen/mainwindow.py +++ b/src/nailab/ui_gen/mainwindow.py @@ -2,16 +2,19 @@ # Form implementation generated from reading ui file 'ui/mainwindow.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(1060, 587) + MainWindow.resize(1060, 588) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) @@ -23,7 +26,7 @@ class Ui_MainWindow(object): self.gridLayout.addWidget(self.tabs, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1060, 19)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1060, 21)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -69,6 +72,8 @@ class Ui_MainWindow(object): 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.actionSettings = QtWidgets.QAction(MainWindow) + self.actionSettings.setObjectName("actionSettings") self.menuFile.addAction(self.actionNew_strategy) self.menuFile.addAction(self.actionOpen_strategy) self.menuFile.addAction(self.actionSave_strategy) @@ -85,6 +90,7 @@ class Ui_MainWindow(object): self.toolBar.addSeparator() self.toolBar.addAction(self.actionExecute) self.toolBar.addAction(self.actionExecute_in_Portfolio_Mode) + self.toolBar.addAction(self.actionSettings) self.retranslateUi(MainWindow) self.tabs.setCurrentIndex(-1) @@ -96,6 +102,7 @@ class Ui_MainWindow(object): self.actionSave_as.triggered.connect(MainWindow.saveStrategyAs) self.actionPerformance.triggered.connect(MainWindow.performanceAnalysis) self.actionExecute_in_Portfolio_Mode.triggered.connect(MainWindow.executeStrategyInPortfolioMode) + self.actionSettings.triggered.connect(MainWindow.showSettingsDialog) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): @@ -117,5 +124,5 @@ class Ui_MainWindow(object): 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")) - + self.actionSettings.setText(_translate("MainWindow", "Settings")) import nailab_rc diff --git a/src/nailab/ui_gen/newdatasourcedialog.py b/src/nailab/ui_gen/newdatasourcedialog.py index 15e714a..5d07b79 100644 --- a/src/nailab/ui_gen/newdatasourcedialog.py +++ b/src/nailab/ui_gen/newdatasourcedialog.py @@ -2,12 +2,15 @@ # Form implementation generated from reading ui file 'ui/newdatasourcedialog.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_NewDataSourceDialog(object): def setupUi(self, NewDataSourceDialog): NewDataSourceDialog.setObjectName("NewDataSourceDialog") @@ -36,6 +39,7 @@ class Ui_NewDataSourceDialog(object): self.cb_sourceType.setSizePolicy(sizePolicy) self.cb_sourceType.setObjectName("cb_sourceType") self.cb_sourceType.addItem("") + self.cb_sourceType.addItem("") self.gridLayout.addWidget(self.cb_sourceType, 1, 1, 1, 1) self.label_2 = QtWidgets.QLabel(NewDataSourceDialog) self.label_2.setObjectName("label_2") @@ -56,5 +60,5 @@ class Ui_NewDataSourceDialog(object): NewDataSourceDialog.setWindowTitle(_translate("NewDataSourceDialog", "New data source")) self.label.setText(_translate("NewDataSourceDialog", "Data source type")) self.cb_sourceType.setItemText(0, _translate("NewDataSourceDialog", "CSV")) + self.cb_sourceType.setItemText(1, _translate("NewDataSourceDialog", "YahooCSV")) self.label_2.setText(_translate("NewDataSourceDialog", "Data source name")) - diff --git a/src/nailab/ui_gen/performancewidget.py b/src/nailab/ui_gen/performancewidget.py index 82d5ce4..183189a 100644 --- a/src/nailab/ui_gen/performancewidget.py +++ b/src/nailab/ui_gen/performancewidget.py @@ -2,12 +2,15 @@ # Form implementation generated from reading ui file 'ui/performancewidget.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_PerformanceWidget(object): def setupUi(self, PerformanceWidget): PerformanceWidget.setObjectName("PerformanceWidget") @@ -77,7 +80,7 @@ class Ui_PerformanceWidget(object): self.gridLayout.addWidget(self.lw_strategies, 0, 0, 1, 2) self.retranslateUi(PerformanceWidget) - self.tab1.setCurrentIndex(1) + self.tab1.setCurrentIndex(0) self.b_add.clicked.connect(PerformanceWidget.addResults) self.b_remove.clicked.connect(PerformanceWidget.removeResults) self.rb_daily.toggled['bool'].connect(PerformanceWidget.dailyToggled) @@ -95,6 +98,5 @@ class Ui_PerformanceWidget(object): 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/settingswindow.py b/src/nailab/ui_gen/settingswindow.py new file mode 100644 index 0000000..6ce07dc --- /dev/null +++ b/src/nailab/ui_gen/settingswindow.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/settingswindow.ui' +# +# Created by: PyQt5 UI code generator 5.15.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SettingsWindow(object): + def setupUi(self, SettingsWindow): + SettingsWindow.setObjectName("SettingsWindow") + SettingsWindow.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(SettingsWindow) + self.gridLayout.setObjectName("gridLayout") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 2, 0, 1, 1) + self.sb_commissionPercentage = QtWidgets.QDoubleSpinBox(SettingsWindow) + self.sb_commissionPercentage.setDecimals(4) + self.sb_commissionPercentage.setObjectName("sb_commissionPercentage") + self.gridLayout.addWidget(self.sb_commissionPercentage, 0, 1, 1, 1) + self.label = QtWidgets.QLabel(SettingsWindow) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(SettingsWindow) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(SettingsWindow) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.sb_commissionPerShare = QtWidgets.QDoubleSpinBox(SettingsWindow) + self.sb_commissionPerShare.setDecimals(4) + self.sb_commissionPerShare.setObjectName("sb_commissionPerShare") + self.gridLayout.addWidget(self.sb_commissionPerShare, 1, 1, 1, 1) + + self.retranslateUi(SettingsWindow) + self.buttonBox.accepted.connect(SettingsWindow.accept) + self.buttonBox.rejected.connect(SettingsWindow.reject) + QtCore.QMetaObject.connectSlotsByName(SettingsWindow) + + def retranslateUi(self, SettingsWindow): + _translate = QtCore.QCoreApplication.translate + SettingsWindow.setWindowTitle(_translate("SettingsWindow", "Settings")) + self.label.setText(_translate("SettingsWindow", "Commission (%)")) + self.label_2.setText(_translate("SettingsWindow", "Commission (per share)")) diff --git a/src/nailab/ui_gen/statisticwidget.py b/src/nailab/ui_gen/statisticwidget.py new file mode 100644 index 0000000..e7796b0 --- /dev/null +++ b/src/nailab/ui_gen/statisticwidget.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/statisticwidget.ui' +# +# Created by: PyQt5 UI code generator 5.15.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_StatisticWidget(object): + def setupUi(self, StatisticWidget): + StatisticWidget.setObjectName("StatisticWidget") + StatisticWidget.resize(548, 418) + self.gridLayout = QtWidgets.QGridLayout(StatisticWidget) + self.gridLayout.setContentsMargins(1, 1, 1, 1) + self.gridLayout.setObjectName("gridLayout") + self.tabWidget = QtWidgets.QTabWidget(StatisticWidget) + self.tabWidget.setObjectName("tabWidget") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.gridLayout_2 = QtWidgets.QGridLayout(self.tab) + self.gridLayout_2.setObjectName("gridLayout_2") + self.tw_byTicker = QtWidgets.QTreeWidget(self.tab) + self.tw_byTicker.setObjectName("tw_byTicker") + self.gridLayout_2.addWidget(self.tw_byTicker, 0, 0, 1, 1) + self.tabWidget.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.tw_seasonality = QtWidgets.QTreeWidget(self.tab_2) + self.tw_seasonality.setObjectName("tw_seasonality") + self.gridLayout_3.addWidget(self.tw_seasonality, 0, 0, 1, 1) + self.tabWidget.addTab(self.tab_2, "") + self.tab_3 = QtWidgets.QWidget() + self.tab_3.setObjectName("tab_3") + self.tabWidget.addTab(self.tab_3, "") + self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) + + self.retranslateUi(StatisticWidget) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(StatisticWidget) + + def retranslateUi(self, StatisticWidget): + _translate = QtCore.QCoreApplication.translate + StatisticWidget.setWindowTitle(_translate("StatisticWidget", "StatisticWidget")) + self.tw_byTicker.setSortingEnabled(True) + self.tw_byTicker.headerItem().setText(0, _translate("StatisticWidget", "Ticker")) + self.tw_byTicker.headerItem().setText(1, _translate("StatisticWidget", "Net profit")) + self.tw_byTicker.headerItem().setText(2, _translate("StatisticWidget", "Total trades")) + self.tw_byTicker.headerItem().setText(3, _translate("StatisticWidget", "Avg. return")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("StatisticWidget", "By ticker")) + self.tw_seasonality.setSortingEnabled(True) + self.tw_seasonality.headerItem().setText(1, _translate("StatisticWidget", "Net profit")) + self.tw_seasonality.headerItem().setText(2, _translate("StatisticWidget", "Total trades")) + self.tw_seasonality.headerItem().setText(3, _translate("StatisticWidget", "Avg. return")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("StatisticWidget", "Seasonal")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("StatisticWidget", "Returns")) diff --git a/src/nailab/ui_gen/strategywidget.py b/src/nailab/ui_gen/strategywidget.py index 6d9f7fd..12ee608 100644 --- a/src/nailab/ui_gen/strategywidget.py +++ b/src/nailab/ui_gen/strategywidget.py @@ -2,16 +2,19 @@ # Form implementation generated from reading ui file 'ui/strategywidget.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_StrategyWidget(object): def setupUi(self, StrategyWidget): StrategyWidget.setObjectName("StrategyWidget") - StrategyWidget.resize(977, 569) + StrategyWidget.resize(977, 666) self.gridLayout_3 = QtWidgets.QGridLayout(StrategyWidget) self.gridLayout_3.setContentsMargins(1, 1, 1, 1) self.gridLayout_3.setObjectName("gridLayout_3") @@ -28,6 +31,7 @@ class Ui_StrategyWidget(object): self.gridLayoutWidget = QtWidgets.QWidget(self.splitter) self.gridLayoutWidget.setObjectName("gridLayoutWidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget) + self.gridLayout_2.setContentsMargins(2, 2, 2, 2) self.gridLayout_2.setObjectName("gridLayout_2") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") @@ -44,16 +48,16 @@ class Ui_StrategyWidget(object): self.b_deleteSource = QtWidgets.QPushButton(self.gridLayoutWidget) self.b_deleteSource.setObjectName("b_deleteSource") self.horizontalLayout.addWidget(self.b_deleteSource) - self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2) - self.dte_to = QtWidgets.QDateTimeEdit(self.gridLayoutWidget) - self.dte_to.setObjectName("dte_to") - self.gridLayout_2.addWidget(self.dte_to, 3, 1, 1, 1) + self.gridLayout_2.addLayout(self.horizontalLayout, 7, 0, 1, 2) self.dte_from = QtWidgets.QDateTimeEdit(self.gridLayoutWidget) self.dte_from.setObjectName("dte_from") - self.gridLayout_2.addWidget(self.dte_from, 2, 1, 1, 1) + self.gridLayout_2.addWidget(self.dte_from, 4, 1, 1, 1) self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_2.setObjectName("label_2") - self.gridLayout_2.addWidget(self.label_2, 3, 0, 1, 1) + self.gridLayout_2.addWidget(self.label_2, 5, 0, 1, 1) + self.dte_to = QtWidgets.QDateTimeEdit(self.gridLayoutWidget) + self.dte_to.setObjectName("dte_to") + self.gridLayout_2.addWidget(self.dte_to, 5, 1, 1, 1) self.rb_allData = QtWidgets.QRadioButton(self.gridLayoutWidget) self.rb_allData.setChecked(True) self.rb_allData.setObjectName("rb_allData") @@ -69,7 +73,7 @@ class Ui_StrategyWidget(object): self.tw_feeds.setHeaderHidden(True) self.tw_feeds.setObjectName("tw_feeds") self.tw_feeds.headerItem().setText(0, "1") - self.gridLayout_2.addWidget(self.tw_feeds, 4, 0, 1, 2) + self.gridLayout_2.addWidget(self.tw_feeds, 6, 0, 1, 2) self.label = QtWidgets.QLabel(self.gridLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) @@ -77,7 +81,7 @@ class Ui_StrategyWidget(object): sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) self.label.setSizePolicy(sizePolicy) self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.gridLayout_2.addWidget(self.label, 4, 0, 1, 1) self.rb_timeWindow = QtWidgets.QRadioButton(self.gridLayoutWidget) self.rb_timeWindow.setObjectName("rb_timeWindow") self.gridLayout_2.addWidget(self.rb_timeWindow, 1, 0, 1, 2) @@ -111,12 +115,11 @@ class Ui_StrategyWidget(object): self.b_addSource.setText(_translate("StrategyWidget", "Add source")) self.b_refresh.setText(_translate("StrategyWidget", "Refresh")) self.b_deleteSource.setText(_translate("StrategyWidget", "Delete source")) - self.dte_to.setDisplayFormat(_translate("StrategyWidget", "dd.MM.yyyy H:mm")) self.dte_from.setDisplayFormat(_translate("StrategyWidget", "dd.MM.yyyy H:mm")) self.label_2.setText(_translate("StrategyWidget", "To")) + self.dte_to.setDisplayFormat(_translate("StrategyWidget", "dd.MM.yyyy H:mm")) self.rb_allData.setText(_translate("StrategyWidget", "All data")) self.label.setText(_translate("StrategyWidget", "From")) self.rb_timeWindow.setText(_translate("StrategyWidget", "Time window")) self.tabs.setTabText(self.tabs.indexOf(self.tab), _translate("StrategyWidget", "Code")) - from PyQt5 import Qsci diff --git a/src/nailab/ui_gen/tradeslistwidget.py b/src/nailab/ui_gen/tradeslistwidget.py index 2b93830..4b5ba5d 100644 --- a/src/nailab/ui_gen/tradeslistwidget.py +++ b/src/nailab/ui_gen/tradeslistwidget.py @@ -2,12 +2,15 @@ # Form implementation generated from reading ui file 'ui/tradeslistwidget.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.15.1 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_TradesListWidget(object): def setupUi(self, TradesListWidget): TradesListWidget.setObjectName("TradesListWidget") @@ -40,4 +43,3 @@ class Ui_TradesListWidget(object): self.trades.headerItem().setText(5, _translate("TradesListWidget", "Exit time")) self.trades.headerItem().setText(6, _translate("TradesListWidget", "Exit price")) self.trades.headerItem().setText(7, _translate("TradesListWidget", "PnL")) - diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 550515f..1e1e33a 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 1060 - 587 + 588 @@ -45,7 +45,7 @@ 0 0 1060 - 19 + 21 @@ -91,6 +91,7 @@ + @@ -166,6 +167,11 @@ Execute in Portfolio Mode + + + Settings + + @@ -299,6 +305,22 @@ + + actionSettings + triggered() + MainWindow + showSettingsDialog() + + + -1 + -1 + + + 529 + 293 + + + openTrades() @@ -310,5 +332,6 @@ saveStrategyAs() performanceAnalysis() executeStrategyInPortfolioMode() + showSettingsDialog() diff --git a/ui/newdatasourcedialog.ui b/ui/newdatasourcedialog.ui index 27dfc1c..4b011b4 100644 --- a/ui/newdatasourcedialog.ui +++ b/ui/newdatasourcedialog.ui @@ -50,6 +50,11 @@ CSV + + + YahooCSV + + diff --git a/ui/performancewidget.ui b/ui/performancewidget.ui index 8394d6e..00b085c 100644 --- a/ui/performancewidget.ui +++ b/ui/performancewidget.ui @@ -29,7 +29,7 @@ - 1 + 0 diff --git a/ui/settingswindow.ui b/ui/settingswindow.ui new file mode 100644 index 0000000..d53d550 --- /dev/null +++ b/ui/settingswindow.ui @@ -0,0 +1,105 @@ + + + SettingsWindow + + + + 0 + 0 + 400 + 300 + + + + Settings + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 4 + + + + + + + Commission (%) + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Commission (per share) + + + + + + + 4 + + + + + + + + + buttonBox + accepted() + SettingsWindow + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsWindow + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/statisticwidget.ui b/ui/statisticwidget.ui new file mode 100644 index 0000000..2ea6ec3 --- /dev/null +++ b/ui/statisticwidget.ui @@ -0,0 +1,113 @@ + + + StatisticWidget + + + + 0 + 0 + 548 + 418 + + + + StatisticWidget + + + + 1 + + + 1 + + + 1 + + + 1 + + + + + 0 + + + + By ticker + + + + + + true + + + + Ticker + + + + + Net profit + + + + + Total trades + + + + + Avg. return + + + + + + + + + Seasonal + + + + + + true + + + + + + + + + Net profit + + + + + Total trades + + + + + Avg. return + + + + + + + + + Returns + + + + + + + + + diff --git a/ui/strategywidget.ui b/ui/strategywidget.ui index 36dd940..f16b7cb 100644 --- a/ui/strategywidget.ui +++ b/ui/strategywidget.ui @@ -7,7 +7,7 @@ 0 0 977 - 569 + 666 @@ -55,7 +55,19 @@ - + + 2 + + + 2 + + + 2 + + + 2 + + @@ -106,27 +118,27 @@ - - - - dd.MM.yyyy H:mm - - - - + dd.MM.yyyy H:mm - + To + + + + dd.MM.yyyy H:mm + + + @@ -137,7 +149,7 @@ - + @@ -161,7 +173,7 @@ - +