Source code for UI.ShareTradingFrame

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 Callbacks
from .AddTradeDialogWindow import AddTradeDialogWindow
from .ConfirmWindow import ConfirmWindow

INVALID_STRING = "-"

[docs]class ShareTradingFrame(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) self.parent = parent self.callbacks = {} self._create_UI() def set_callback(self, id, callback): self.callbacks[id] = callback def _create_UI(self): # Create frame containing buttons on the top row of the frame buttonsFrame = ttk.Frame(self, relief="groove", borderwidth=1) buttonsFrame.pack(fill="x", expand=True, anchor="n") # Add buttons for the share trading page self.open_icon = tk.PhotoImage(file=currentdir + "/assets/open_file_icon.png").subsample(2) openButton = ttk.Button(buttonsFrame, text="Open Portfolio...", command=self._open_portfolio) openButton.pack(side="left", anchor="n", padx=5, pady=5) openButton.config(image=self.open_icon) self.save_icon = tk.PhotoImage(file=currentdir + "/assets/save_file_icon.png").subsample(2) saveButton = ttk.Button(buttonsFrame, text="Save Portfolio...", command=self._save_portfolio) saveButton.pack(side="left", anchor="n", padx=5, pady=5) saveButton.config(image=self.save_icon) self.add_icon = tk.PhotoImage(file=currentdir + "/assets/add_icon.png").subsample(2) addTradeButton = ttk.Button(buttonsFrame, text="Add Trade...", command=self._display_add_trade_panel) addTradeButton.pack(side="left", anchor="n", padx=5, pady=5) addTradeButton.config(image=self.add_icon) self.autoRefresh = tk.IntVar(value=1) self.autoRefreshCheckBox = ttk.Checkbutton(buttonsFrame, text="Auto", variable=self.autoRefresh, command=self.set_auto_refresh, onvalue=1, offvalue=0) self.autoRefreshCheckBox.pack(side="right", anchor="n", padx=5, pady=5) self.refreshButton = ttk.Button(buttonsFrame, text="Refresh", command=self._refresh_live_data) self.refreshButton.pack(side="right", anchor="n", padx=5, pady=5) # This frame is a container for smaller frames displaying balances balancesFrame = ttk.Frame(self) balancesFrame.pack(fill="none", expand=True, anchor="n", pady=5) # Create frame containing fund balances fundBalFrame = ttk.Frame(balancesFrame, relief="groove", borderwidth=1) fundBalFrame.pack(side="left", fill="y", anchor="n", pady=5) # Create four different frames for cash, portfolio, total and profit/loss cashFrame = ttk.Frame(fundBalFrame) cashFrame.pack(side="left", fill="y", anchor="n", padx=10, pady=5) cashLabel = ttk.Label(cashFrame, text="Cash [£]") cashLabel.pack(side="top") self.cashStringVar = tk.StringVar() cashValueLabel = ttk.Label(cashFrame, textvariable=self.cashStringVar) cashValueLabel.pack(side="bottom") # Portfolio balance frame portfolioFrame = ttk.Frame(fundBalFrame) portfolioFrame.pack(side="left", fill="y", anchor="n", padx=10, pady=5) portfolioLabel = ttk.Label(portfolioFrame, text="Portfolio [£]") portfolioLabel.pack(side="top") self.portfolioStringVar = tk.StringVar() portfolioValueLabel = ttk.Label(portfolioFrame, textvariable=self.portfolioStringVar) portfolioValueLabel.pack(side="bottom") # Total value balance frame totalFrame = ttk.Frame(fundBalFrame) totalFrame.pack(side="left", fill="both", anchor="n", padx=10, pady=5) totalLabel = ttk.Label(totalFrame, text="Total [£]") totalLabel.pack(side="top") self.totalStringVar = tk.StringVar() totalValueLabel = ttk.Label(totalFrame, textvariable=self.totalStringVar) totalValueLabel.pack(side="bottom") # profits balance frames plFrame = ttk.Frame(fundBalFrame) plFrame.pack(side="left", fill="both", anchor="n", padx=10, pady=5) plLabel = ttk.Label(plFrame, text="P/L [£]") plLabel.pack(side="top") self.plStringVar = tk.StringVar() self.plStringVar.trace_add('write', self._update_pl_label_background) self.plValueLabel = ttk.Label(plFrame, textvariable=self.plStringVar) self.plValueLabel.pack(side="bottom") plpcFrame = ttk.Frame(fundBalFrame) plpcFrame.pack(side="left", fill="both", anchor="n", padx=10, pady=5) plpcLabel = ttk.Label(plpcFrame, text="P/L [%]") plpcLabel.pack(side="top") self.plpcStringVar = tk.StringVar() self.plpcStringVar.trace_add('write', self._update_pl_label_background) self.plpcValueLabel = ttk.Label(plpcFrame, textvariable=self.plpcStringVar) self.plpcValueLabel.pack(side="bottom") # Create frame containing open positions P/L holdBalFrame = ttk.Frame(balancesFrame, relief="groove", borderwidth=1) holdBalFrame.pack(side="left", fill="y", anchor="n", padx=5, pady=5) # holding P/L of current positions holdPlFrame = ttk.Frame(holdBalFrame) holdPlFrame.pack(side="left", fill="y", anchor="n", padx=10, pady=5) holdPlLabel = ttk.Label(holdPlFrame, text="Holding P/L [£]") holdPlLabel.pack(side="top") self.holdPLStringVar = tk.StringVar() self.holdPLStringVar.trace_add('write', self._update_pl_label_background) self.holdPLValueLabel = ttk.Label(holdPlFrame, textvariable=self.holdPLStringVar) self.holdPLValueLabel.pack(side="bottom") holdPlPcFrame = ttk.Frame(holdBalFrame) holdPlPcFrame.pack(side="left", fill="y", anchor="n", padx=10, pady=5) holdPlPcLabel = ttk.Label(holdPlPcFrame, text="Holding P/L [%]") holdPlPcLabel.pack(side="top") self.holdPlPcStringVar = tk.StringVar() self.holdPlPcStringVar.trace_add('write', self._update_pl_label_background) self.holdPlPcValueLabel = ttk.Label(holdPlPcFrame, textvariable=self.holdPlPcStringVar) self.holdPlPcValueLabel.pack(side="bottom") # Frame containing the holdings table holdingsFrame = ttk.Frame(self, relief="groove", borderwidth=1) holdingsFrame.pack(fill="x", expand=True, anchor="n") # Title label currLabel = ttk.Label(holdingsFrame, text="Portfolio") currLabel.pack() # Create a table for the current data self.currentDataTreeView = ttk.Treeview(holdingsFrame) self.currentDataTreeView.pack(fill='x') self.currentDataTreeView["columns"] = ('quantity','open','last','cost','value','pl','pl_pc') self.currentDataTreeView.heading("#0", text='Symbol', anchor='w') self.currentDataTreeView.heading("quantity", text='Quantity', anchor='w') self.currentDataTreeView.heading("open", text='Open [p]', anchor='w') self.currentDataTreeView.heading("last", text='Last [p]', anchor='w') self.currentDataTreeView.heading("cost", text='Cost [£]', anchor='w') self.currentDataTreeView.heading("value", text='Value [£]', anchor='w') self.currentDataTreeView.heading("pl", text='P/L £', anchor='w') self.currentDataTreeView.heading("pl_pc", text='P/L %', anchor='w') self.currentDataTreeView.column("#0", width=100) self.currentDataTreeView.column("quantity", width=100) self.currentDataTreeView.column("open", width=100) self.currentDataTreeView.column("last", width=100) self.currentDataTreeView.column("cost", width=100) self.currentDataTreeView.column("value", width=100) self.currentDataTreeView.column("pl", width=100) self.currentDataTreeView.column("pl_pc", width=100) # Treeview colour layout self.currentDataTreeView.tag_configure('profit', background='lightgreen') self.currentDataTreeView.tag_configure('loss', background='LightPink1') self.currentDataTreeView.tag_configure('invalid', background='yellow') # Frame containing the trading history logFrame = ttk.Frame(self, relief="groove", borderwidth=1) logFrame.pack(fill="x", expand=True, anchor="n") # Title label logLabel = ttk.Label(logFrame, text="Trades History") logLabel.pack() # Use a Frame as container for the treeview and the scrollbar tableFrame = ttk.Frame(logFrame) tableFrame.pack(fill="x") # Create a table for the trading log self.logTreeView = ttk.Treeview(tableFrame) self.logTreeView.pack(fill='x', side="left", expand=True) self.logTreeView["columns"] = ('action','symbol','quantity','price','fee', 'stamp_duty','total') self.logTreeView.heading("#0", text='Date', anchor='w') self.logTreeView.heading("action", text='Action', anchor='w') self.logTreeView.heading("symbol", text='Symbol', anchor='w') self.logTreeView.heading("quantity", text='Quantity', anchor='w') self.logTreeView.heading("price", text='Price [p]', anchor='w') self.logTreeView.heading("fee", text='Fee [£]', anchor='w') self.logTreeView.heading("stamp_duty", text='Stamp Duty [%]', anchor='w') self.logTreeView.heading("total", text='Total [£]', anchor='w') self.logTreeView.column("#0", width=100) self.logTreeView.column("action", width=100) self.logTreeView.column("symbol", width=100) self.logTreeView.column("quantity", width=100) self.logTreeView.column("price", width=100) self.logTreeView.column("fee", width=100) self.logTreeView.column("stamp_duty", width=100) self.logTreeView.column("total", width=100) # Treeview colour layout self.logTreeView.tag_configure('oddrow', background='white') self.logTreeView.tag_configure('evenrow', background='lightblue') # Create a scrollbar for the history log scrollBar = tk.Scrollbar(tableFrame, orient="vertical", command=self.logTreeView.yview) scrollBar.pack(side='right', fill='y') self.logTreeView.configure(yscrollcommand=scrollBar.set) # Create popup menu for the trade history log self.logPopupMenu = tk.Menu(self.logTreeView, tearoff=0) self.logPopupMenu.add_command(label="Add trade...", command=self._display_add_trade_panel) self.logPopupMenu.add_command(label="Delete last...", command=self._delete_last_trade) self.logTreeView.bind("<Button-3>", self._trade_log_popup_menu_event) def _trade_log_popup_menu_event(self, event): self.logPopupMenu.tk_popup(event.x_root, event.y_root) def _display_add_trade_panel(self): AddTradeDialogWindow(self.parent, self._on_add_new_trade_event) def _delete_last_trade(self): ConfirmWindow(self.parent, "Confirm", "Are you sure?", self.callbacks[Callbacks.ON_DELETE_LAST_TRADE_EVENT]) def _on_add_new_trade_event(self, newTrade): return self.callbacks[Callbacks.ON_NEW_TRADE_EVENT](newTrade) def _refresh_live_data(self): # Disable the refresh inputs temporarily self.refreshButton.config(state="disabled") self.autoRefreshCheckBox.config(state="disabled") # Notify the Controller to request new data self.callbacks[Callbacks.ON_MANUAL_REFRESH_EVENT]() def _check_string_value(self, var): valid_var = var if var is None or len(var) < 1: valid_var = INVALID_STRING return valid_var def _check_float_value(self, var, valid=True, canBeNegative=False): valid_var = var if var is None or (not canBeNegative and var < 0) or not valid: valid_var = INVALID_STRING else: valid_var = round(var, 3) return valid_var def _update_pl_label_background(self, *args): try: value = float(self.plStringVar.get()) self.plValueLabel.config(foreground = "green" if value >= 0.0 else "red") except: pass try: value = float(self.plpcStringVar.get()) self.plpcValueLabel.config(foreground = "green" if value >= 0.0 else "red") except: pass try: value = float(self.holdPLStringVar.get()) self.holdPLValueLabel.config(foreground = "green" if value >= 0.0 else "red") except: pass try: value = float(self.holdPlPcStringVar.get()) self.holdPlPcValueLabel.config(foreground = "green" if value >= 0.0 else "red") except: pass def _open_portfolio(self): self.callbacks[Callbacks.ON_OPEN_LOG_FILE_EVENT]() def _save_portfolio(self): self.callbacks[Callbacks.ON_SAVE_LOG_FILE_EVENT]() def _update_refresh_button_state(self): # Disable the Refresh button when AutoRefresh is active self.autoRefreshCheckBox.config(state="enabled") # reset status of checkbox value = self.autoRefresh.get() self.refreshButton.config(state="disabled" if value == 1 else "enabled") def set_auto_refresh(self): self._update_refresh_button_state() value = self.autoRefresh.get() self.callbacks[Callbacks.ON_SET_AUTO_REFRESH_EVENT](bool(value)) def add_entry_to_log_table(self, trade): v_date = self._check_string_value(trade.date.strftime('%d/%m/%Y')) v_act = self._check_string_value(trade.action.name) v_sym = self._check_string_value(trade.symbol) v_am = self._check_float_value(trade.quantity) v_pri = self._check_float_value(trade.price) v_fee = self._check_float_value(trade.fee) v_sd = self._check_float_value(trade.sdr) v_tot = self._check_float_value(trade.total, canBeNegative=True) tag = "evenrow" if len(self.logTreeView.get_children()) % 2 == 0 else "oddrow" self.logTreeView.insert('', 'end', text=v_date, values=(v_act,v_sym,v_am,v_pri,v_fee,v_sd,v_tot), tags=(tag,)) def update_share_trading_holding(self, symbol, quantity, openPrice, lastPrice, cost, value, pl, plPc, validity): v_symbol=self._check_string_value(symbol) v_quantity=self._check_float_value(quantity) v_openPrice=self._check_float_value(openPrice) v_lastPrice=self._check_float_value(lastPrice, valid=validity) v_cost=self._check_float_value(cost) v_value=self._check_float_value(value, valid=validity) v_pl=self._check_float_value(pl, valid=validity, canBeNegative=True) v_plPc=self._check_float_value(plPc, valid=validity, canBeNegative=True) tag = "invalid" if validity: tag = "profit" if plPc >= 0 else "loss" found = False for child in self.currentDataTreeView.get_children(): item = self.currentDataTreeView.item(child) s = item['text'] if v_symbol == s: found = True self.currentDataTreeView.item(child, values=(v_quantity, v_openPrice, v_lastPrice, v_cost, v_value, v_pl, v_plPc), tags=(tag,)) break if not found: #tag = "evenrow" if len(self.currentDataTreeView.get_children()) % 2 == 0 else "oddrow" self.currentDataTreeView.insert('','end',text=symbol, values=(v_quantity, v_openPrice, v_lastPrice, v_cost, v_value, v_pl, v_plPc), tags=(tag,)) def update_portfolio_balances(self, cash, holdingsValue, totalValue, pl, plPerc, holdingPL, holdingPLPC, valid): v_cash = self._check_float_value(cash) v_holdVal = self._check_float_value(holdingsValue, valid=valid) v_tot = self._check_float_value(totalValue, valid=valid) v_pl = self._check_float_value(pl, valid=valid, canBeNegative=True) v_plPerc = self._check_float_value(plPerc, valid=valid, canBeNegative=True) v_holdPl = self._check_float_value(holdingPL, valid=valid, canBeNegative=True) v_holdPlPc = self._check_float_value(holdingPLPC, valid=valid, canBeNegative=True) self.cashStringVar.set(str(v_cash)) self.portfolioStringVar.set(str(v_holdVal)) self.totalStringVar.set(str(v_tot)) self.plStringVar.set(str(v_pl)) self.plpcStringVar.set(str(v_plPerc)) self.holdPLStringVar.set(str(v_holdPl)) self.holdPlPcStringVar.set(str(v_holdPlPc)) def reset_view(self, resetHistory=False): self._update_refresh_button_state() self.cashStringVar.set(str(INVALID_STRING)) self.portfolioStringVar.set(str(INVALID_STRING)) self.totalStringVar.set(str(INVALID_STRING)) self.plStringVar.set(str(INVALID_STRING)) self.plpcStringVar.set(str(INVALID_STRING)) self.holdPLStringVar.set(str(INVALID_STRING)) self.holdPlPcStringVar.set(str(INVALID_STRING)) self.currentDataTreeView.delete(*self.currentDataTreeView.get_children()) if resetHistory: self.logTreeView.delete(*self.logTreeView.get_children())