Skip to content

Commit

Permalink
Merge pull request #31 from ilcardella/settings
Browse files Browse the repository at this point in the history
Settings
  • Loading branch information
ilcardella committed May 3, 2019
2 parents e53c8c8 + 35e4163 commit 9c4751e
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 63 deletions.
1 change: 0 additions & 1 deletion config/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"general": {
"debug_log": false,
"trading_log_path": "{home}/.TradingMate/trading_log.json",
"credentials_filepath": "{home}/.TradingMate/config/.credentials"
},
Expand Down
4 changes: 2 additions & 2 deletions src/Model/DatabaseHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def read_data(self, filepath=None):
- **filepath**: optional, if not set the configured path will be used
"""
path = filepath if filepath is not None else self.db_filepath
path = filepath.replace('{home}', Utils.get_home_path()) if filepath is not None else self.db_filepath
logging.info('DatabaseHandler - reading data from {}'.format(path))
self.db_filepath = path
json_obj = Utils.load_json_file(path)
Expand All @@ -50,7 +50,7 @@ def write_data(self, filepath=None):
"""
Write the trade history to the database
"""
path = filepath if filepath is not None else self.db_filepath
path = filepath.replace('{home}', Utils.get_home_path()) if filepath is not None else self.db_filepath
logging.info('DatabaseHandler - writing data to {}'.format(path))
# Create a json object and store the trade history into it
json_obj = {
Expand Down
1 change: 1 addition & 0 deletions src/Model/Portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def clear(self):
self._cash_available = 0
self._cash_deposited = 0
self._holdings.clear()
self.price_getter.reset()
logging.info('Portfolio cleared')

def reload(self, trades_list):
Expand Down
30 changes: 16 additions & 14 deletions src/Model/StockPriceGetter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,14 @@ class StockPriceGetter(TaskThread):

def __init__(self, config, onNewPriceDataCallback):
TaskThread.__init__(self)
self._read_configuration(config)
self.config = config
self.onNewPriceDataCallback = onNewPriceDataCallback
self.lastData = {}
self.symbolList = []
self.reset()
logging.info('StockPriceGetter initialised')

def _read_configuration(self, config):
self.alphaVantageAPIKey = config.get_alpha_vantage_api_key()
self.alphaVantageBaseURL = config.get_alpha_vantage_base_url()
def _read_configuration(self):
# Override the parent class default value
self._interval = config.get_alpha_vantage_polling_period()
self._interval = self.config.get_alpha_vantage_polling_period()

def task(self):
priceDict = {}
Expand All @@ -47,8 +44,9 @@ def task(self):
def _fetch_price_data(self, symbol):
try:
url = self._build_url("TIME_SERIES_DAILY",
symbol, "5min", self.alphaVantageAPIKey)
except Exception:
symbol, "5min", self.config.get_alpha_vantage_api_key())
except Exception as e:
logging.error(e)
logging.error(
'StockPriceGetter - Unable to build url for {}'.format(symbol))
return None
Expand All @@ -69,11 +67,10 @@ def _fetch_price_data(self, symbol):
return value

def _build_url(self, aLength, aSymbol, anInterval, anApiKey):
function = "function=" + aLength
symbol = "symbol=" + self.convert_market_to_alphavantage(aSymbol)
apiKey = "apikey=" + anApiKey
url = self.alphaVantageBaseURL + "?" + function + "&" + symbol + "&" + apiKey
return url
function = "function={}".format(aLength)
symbol = "symbol={}".format(self.convert_market_to_alphavantage(aSymbol))
apiKey = "apikey={}".format(anApiKey)
return '{}?{}&{}&{}'.format(self.config.get_alpha_vantage_base_url(), function, symbol, apiKey)

def convert_market_to_alphavantage(self, symbol):
"""
Expand All @@ -90,3 +87,8 @@ def get_last_data(self):

def set_symbol_list(self, aList):
self.symbolList = aList

def reset(self):
self._read_configuration()
self.lastData = {}
self.symbolList = []
26 changes: 22 additions & 4 deletions src/TradingMate.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def register_callbacks(self):
"""
Register all the callback functions
"""
# TODO instead of a callback, set a timer that calls a getter every x seconds
self.portfolio.set_callback(
Callbacks.UPDATE_LIVE_PRICES, self.on_update_live_price)
# Init the view
Expand All @@ -73,6 +72,10 @@ def register_callbacks(self):
Callbacks.ON_SAVE_LOG_FILE_EVENT, self.on_save_portfolio_event)
self.view.set_callback(
Callbacks.ON_DELETE_LAST_TRADE_EVENT, self.on_delete_last_trade_event)
self.view.set_callback(
Callbacks.ON_SHOW_SETTINGS_EVENT, self.on_show_settings_event)
self.view.set_callback(
Callbacks.ON_SAVE_SETTINGS_EVENT, self.on_save_settings_event)
logging.info('TradingMate - callbacks registered')

def start(self):
Expand All @@ -96,8 +99,6 @@ def _update_share_trading_view(self, updateHistory=False):
Collect data from the model and update the view
"""
self.view.reset_view(updateHistory)
# Update the database filepath shown in the share trading frame
self.view.set_db_filepath(self.db_handler.get_db_filepath())
# Update history table if required
if updateHistory:
logAsList = self.db_handler.get_trades_list()[
Expand Down Expand Up @@ -126,6 +127,7 @@ def on_close_view_event(self):
"""
Callback function to handle close event of the user interface
"""
logging.info('UserInterface main window closed')
self.portfolio.stop()
self.db_handler.write_data()
logging.info('TradingMate stop')
Expand Down Expand Up @@ -191,9 +193,25 @@ def on_open_portfolio_event(self, filepath):

def on_save_portfolio_event(self, filepath):
"""
Callback function to handler request to save/export the portfolio
Callback function to handle request to save/export the portfolio
"""
logging.info(
'TradingMate - save portfolio request to {}'.format(filepath))
# Write data into the database
self.db_handler.write_data(filepath=filepath)

def on_show_settings_event(self):
"""
Callback to handle request to show the settings panel
"""
return self.configurationManager.get_editable_config()

def on_save_settings_event(self, config):
"""
Callback to save edited settings
"""
self.configurationManager.save_settings(config)
self.db_handler.read_data(self.configurationManager.get_trading_database_path())
self.portfolio.reload(self.db_handler.get_trades_list())
self._update_share_trading_view(updateHistory=True)
logging.info('TradingMate - application reloaded')
61 changes: 61 additions & 0 deletions src/UI/SettingsWindow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import sys
import inspect
import tkinter as tk
from tkinter import ttk

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)

from Utils.Utils import Actions, Markets
from Utils.Trade import Trade
from .WarningWindow import WarningWindow

class SettingsWindow(tk.Toplevel):
"""
Settings panel window
"""
def __init__(self, master, config, save_callback):
tk.Toplevel.__init__(self)
self.master = master
self.config = config
self.save_cb = save_callback
self.transient(self.master)
self.title("Settings")
self.geometry("+%d+%d" % (self.master.winfo_rootx()+400, self.master.winfo_rooty()+100))
self.protocol("WM_DELETE_WINDOW", self.destroy)
self.grab_set()
self.focus_set()
self.create_UI()

def create_UI(self):
ttk.Label(self, text="Trading log path:").grid(row=0, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Credentials path:").grid(row=1, sticky="w", padx=5, pady=5)

self.trading_log_string = tk.StringVar()
#self.trading_log_string.trace_add('write', self.check_data_validity)
self.e_log_path = ttk.Entry(self, width=50, textvariable=self.trading_log_string)
self.e_log_path.grid(row=0, column=1, sticky="e", padx=5, pady=5)

self.credentials_string = tk.StringVar()
#self.credentials_string.trace_add('write', self.check_data_validity)
self.e_cred_path = ttk.Entry(self, width=50, textvariable=self.credentials_string)
self.e_cred_path.grid(row=1, column=1, sticky="e", padx=5, pady=5)

self.trading_log_string.set(self.config['general']['trading_log_path'])
self.credentials_string.set(self.config['general']['credentials_filepath'])

cancelButton = ttk.Button(self, text="Cancel", command=self.destroy)
cancelButton.grid(row=2, column=0, sticky="e", padx=5, pady=5)
addButton = ttk.Button(self, text="Save", command=self.save)
addButton.grid(row=2, column=1, sticky="e", padx=5, pady=5)

def save(self):
self.config['general']['trading_log_path'] = self.trading_log_string.get()
self.config['general']['credentials_filepath'] = self.credentials_string.get()
try:
self.save_cb(self.config)
self.destroy()
except Exception as e:
WarningWindow(self, "Warning", e)
23 changes: 2 additions & 21 deletions src/UI/ShareTradingFrame.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import inspect
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)

from Utils.Utils import Callbacks
from .AddTradeDialogWindow import AddTradeDialogWindow
from .WarningWindow import WarningWindow
from .ConfirmWindow import ConfirmWindow

INVALID_STRING = "-"
Expand Down Expand Up @@ -54,10 +52,6 @@ def _create_UI(self):
self.refreshButton = ttk.Button(buttonsFrame, text="Refresh", command=self._refresh_live_data)
self.refreshButton.pack(side="right", anchor="n", padx=5, pady=5)

self.dbFilepathStringVar = tk.StringVar()
dbLabel = ttk.Label(self, textvariable=self.dbFilepathStringVar)
dbLabel.pack(anchor="w")

# This frame is a container for smaller frames displaying balances
balancesFrame = ttk.Frame(self)
balancesFrame.pack(fill="none", expand=True, anchor="n", pady=5)
Expand Down Expand Up @@ -257,20 +251,10 @@ def _update_pl_label_background(self, *args):
pass

def _open_portfolio(self):
# Open a saved portfolio
filename = filedialog.askopenfilename(initialdir="/home/",title="Select file",filetypes=(("json files","*.json"),("all files","*.*")))
if filename is not None and len(filename) > 0:
result = self.callbacks[Callbacks.ON_OPEN_LOG_FILE_EVENT](filename)
if result["success"] == False:
WarningWindow(self.parent, "Warning", result["message"])
self.callbacks[Callbacks.ON_OPEN_LOG_FILE_EVENT]()

def _save_portfolio(self):
# Save the current log
filename = filedialog.asksaveasfilename(initialdir="/home/",title="Select file",filetypes=(("json files","*.json"),("all files","*.*")))
if filename is not None and len(filename) > 0:
result = self.callbacks[Callbacks.ON_SAVE_LOG_FILE_EVENT](filename)
if result["success"] == False:
WarningWindow(self.parent, "Warning", result["message"])
self.callbacks[Callbacks.ON_SAVE_LOG_FILE_EVENT]()

def _update_refresh_button_state(self):
# Disable the Refresh button when AutoRefresh is active
Expand Down Expand Up @@ -360,6 +344,3 @@ def reset_view(self, resetHistory=False):
self.currentDataTreeView.delete(*self.currentDataTreeView.get_children())
if resetHistory:
self.logTreeView.delete(*self.logTreeView.get_children())

def set_db_filepath(self, filepath):
self.dbFilepathStringVar.set("DB: " + filepath)
34 changes: 22 additions & 12 deletions src/UI/View.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
import tkinter as tk
from tkinter import ttk
from tkinter import StringVar
from tkinter import filedialog

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)

from Utils.Utils import Callbacks, Messages
from Utils.Utils import Callbacks, Messages, Utils
from .WarningWindow import WarningWindow
from .ShareTradingFrame import ShareTradingFrame
from .SettingsWindow import SettingsWindow

APP_NAME = "TradingMate"

Expand Down Expand Up @@ -44,6 +46,9 @@ def create_menu(self):
self.menubar = tk.Menu(self.mainWindow)
# Menu File
filemenu = tk.Menu(self.menubar, tearoff=0)
filemenu.add_command(label="Open...", command=self.on_open_portfolio_event)
filemenu.add_command(label="Export...", command=self.on_save_portfolio_event)
filemenu.add_command(label="Settings...", command=self.on_show_settings)
filemenu.add_command(label="Exit", command=self.on_close_event)
self.menubar.add_cascade(label="File", menu=filemenu)
# Menu About
Expand Down Expand Up @@ -110,29 +115,34 @@ def update_share_trading_portfolio_balances(self, cash, holdingsValue, totalValu
def update_share_trading_holding(self, symbol, quantity, openPrice, lastPrice, cost, value, pl, plPc, validity):
self.shareTradingFrame.update_share_trading_holding(symbol, quantity, openPrice, lastPrice, cost, value, pl, plPc, validity)

def set_db_filepath(self, filepath):
self.shareTradingFrame.set_db_filepath(filepath)

def set_auto_refresh_event(self, value):
self.callbacks[Callbacks.ON_SET_AUTO_REFRESH_EVENT](value)

def on_open_portfolio_event(self, filename):
def on_open_portfolio_event(self):
try:
self.callbacks[Callbacks.ON_OPEN_LOG_FILE_EVENT](filename)
return {'success': True, 'message': 'ok'}
filename = filedialog.askopenfilename(initialdir=Utils.get_home_path(
), title="Select file", filetypes=(("json files", "*.json"), ("all files", "*.*")))
if filename is not None and len(filename) > 0:
self.callbacks[Callbacks.ON_OPEN_LOG_FILE_EVENT](filename)
except RuntimeError as e:
return {'success': False, 'message': e}
WarningWindow(self.parent, "Warning", e)

def on_save_portfolio_event(self, filename):
def on_save_portfolio_event(self):
try:
self.callbacks[Callbacks.ON_SAVE_LOG_FILE_EVENT](filename)
return {'success': True, 'message': 'ok'}
filename = filedialog.asksaveasfilename(initialdir=Utils.get_home_path(
),title="Select file",filetypes=(("json files","*.json"),("all files","*.*")))
if filename is not None and len(filename) > 0:
self.callbacks[Callbacks.ON_SAVE_LOG_FILE_EVENT](filename)
except RuntimeError as e:
return {'success': False, 'message': e}
WarningWindow(self.parent, "Warning", e)

def on_delete_last_trade_event(self):
try:
self.callbacks[Callbacks.ON_DELETE_LAST_TRADE_EVENT]()
return {'success': True, 'message': 'ok'}
except RuntimeError as e:
return {'success': False, 'message': e}

def on_show_settings(self):
config = self.callbacks[Callbacks.ON_SHOW_SETTINGS_EVENT]()
SettingsWindow(self.mainWindow, config, self.callbacks[Callbacks.ON_SAVE_SETTINGS_EVENT])

0 comments on commit 9c4751e

Please sign in to comment.