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