Skip to content

Commit

Permalink
Merge pull request #27 from ilcardella/ui-fixes
Browse files Browse the repository at this point in the history
Ui fixes
  • Loading branch information
ilcardella committed Feb 11, 2019
2 parents fc6fc55 + 767835e commit 38cf93c
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 156 deletions.
24 changes: 12 additions & 12 deletions src/Model/Holding.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

class Holding():

def __init__(self, symbol, amount, open_price=None):
if amount is None or amount < 1:
raise ValueError("Invalid amount")
def __init__(self, symbol, quantity, open_price=None):
if quantity is None or quantity < 1:
raise ValueError("Invalid quantity")
if open_price is not None and open_price < 0:
raise ValueError('Invalid open_price')
self._symbol = symbol
self._amount = amount
self._quantity = quantity
self._openPrice = open_price
self._lastPrice = None
self._lastPriceValid = False
Expand All @@ -30,16 +30,16 @@ def set_open_price(self, price):
raise ValueError("Invalid price")
self._openPrice = price

def set_amount(self, value):
def set_quantity(self, value):
if value is None or value < 1:
raise ValueError("Invalid amount")
self._amount = value
raise ValueError("Invalid quantity")
self._quantity = value

def add_quantity(self, value):
"""
Add or subtract (if value is negative) the value to the holding quantity
"""
self._amount += value
self._quantity += value

def set_last_price_invalid(self):
self._lastPriceValid = False
Expand All @@ -53,18 +53,18 @@ def get_last_price(self):
def get_open_price(self):
return self._openPrice

def get_amount(self):
return self._amount
def get_quantity(self):
return self._quantity

def get_cost(self):
if self._openPrice is None:
return None
return self._amount * (self._openPrice/100) # £
return self._quantity * (self._openPrice/100) # £

def get_value(self):
if self._lastPrice is None:
return None
return self._amount * (self._lastPrice/100) # £
return self._quantity * (self._lastPrice/100) # £

def get_profit_loss(self):
value = self.get_value()
Expand Down
44 changes: 22 additions & 22 deletions src/Model/Portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, name, config):
# Amount of free cash available
self._cash_available = 0
# Overall amount of cash deposited - withdrawed
self._invested_amount = 0
self._cash_deposited = 0
# Data structure to store stock holdings: {"symbol": Holding}
self._holdings = {}
# DataStruct containing the callbacks
Expand All @@ -44,12 +44,12 @@ def get_name(self):
return self._name

def get_cash_available(self):
"""Return the available cash amount in the portfolio [int]"""
"""Return the available cash quantity in the portfolio [int]"""
return self._cash_available

def get_invested_amount(self):
"""Return the total invested amount in the portfolio [int]"""
return self._invested_amount
def get_cash_deposited(self):
"""Return the amount of cash deposited in the portfolio [int]"""
return self._cash_deposited

def get_holding_list(self):
"""Return a list of Holding instances held in the portfolio sorted alphabetically"""
Expand All @@ -59,10 +59,10 @@ def get_holding_symbols(self):
"""Return a list containing the holding symbols as [string] sorted alphabetically"""
return list(sorted(self._holdings.keys()))

def get_holding_amount(self, symbol):
"""Return the amount held for the given symbol"""
def get_holding_quantity(self, symbol):
"""Return the quantity held for the given symbol"""
if symbol in self._holdings:
return self._holdings[symbol].get_amount()
return self._holdings[symbol].get_quantity()
else:
return 0

Expand Down Expand Up @@ -98,20 +98,20 @@ def get_holdings_value(self):

def get_portfolio_pl(self):
"""
Return the profit/loss in £ of the portfolio over the invested amount
Return the profit/loss in £ of the portfolio over the deposited cash
"""
value = self.get_total_value()
invested = self.get_invested_amount()
invested = self.get_cash_deposited()
if value is None or invested is None:
return None
return value - invested

def get_portfolio_pl_perc(self):
"""
Return the profit/loss in % of the portfolio over the invested amount
Return the profit/loss in % of the portfolio over deposited cash
"""
pl = self.get_portfolio_pl()
invested = self.get_invested_amount()
invested = self.get_cash_deposited()
if pl is None or invested is None or invested < 1:
return None
return (pl / invested) * 100
Expand Down Expand Up @@ -160,7 +160,7 @@ def clear(self):
Reset the Portfolio clearing all data
"""
self._cash_available = 0
self._invested_amount = 0
self._cash_deposited = 0
self._holdings.clear()

def reload(self, trades_list):
Expand All @@ -175,10 +175,10 @@ def reload(self, trades_list):
if trade.action == Actions.DEPOSIT or trade.action == Actions.DIVIDEND:
self._cash_available += trade.quantity
if trade.action == Actions.DEPOSIT:
self._invested_amount += trade.quantity
self._cash_deposited += trade.quantity
elif trade.action == Actions.WITHDRAW:
self._cash_available -= trade.quantity
self._invested_amount -= trade.quantity
self._cash_deposited -= trade.quantity
elif trade.action == Actions.BUY:
if trade.symbol not in self._holdings:
self._holdings[trade.symbol] = Holding(trade.symbol, trade.quantity)
Expand All @@ -190,7 +190,7 @@ def reload(self, trades_list):
self._cash_available -= totalCost
elif trade.action == Actions.SELL:
self._holdings[trade.symbol].add_quantity(-trade.quantity) # negative
if self._holdings[trade.symbol].get_amount() < 1:
if self._holdings[trade.symbol].get_quantity() < 1:
del self._holdings[trade.symbol]
profit = ((trade.price/100) * trade.quantity) - trade.fee
self._cash_available += profit
Expand All @@ -207,19 +207,19 @@ def compute_avg_holding_open_price(self, symbol, trades_list):
"""
Return the average price paid to open the current positon of the requested stock.
Starting from the end of the history log, find the BUY transaction that led to
to have the current amount, compute then the average price of these transactions
to have the current quantity, compute then the average price of these transactions
"""
sum = 0
count = 0
targetAmount = self.get_holding_amount(symbol)
if targetAmount == 0:
target = self.get_holding_quantity(symbol)
if target == 0:
return None
for trade in trades_list[::-1]: # reverse order
if trade.symbol == symbol and trade.action == Actions.BUY:
targetAmount -= trade.quantity
target -= trade.quantity
sum += trade.price * trade.quantity
count += trade.quantity
if targetAmount <= 0:
if target <= 0:
break
avg = sum / count
return round(avg, 4)
Expand All @@ -241,7 +241,7 @@ def is_trade_valid(self, newTrade):
logging.error(Messages.INSUF_FUNDING.value)
raise RuntimeError(Messages.INSUF_FUNDING.value)
elif newTrade.action == Actions.SELL:
if newTrade.quantity > self.get_holding_amount(newTrade.symbol):
if newTrade.quantity > self.get_holding_quantity(newTrade.symbol):
logging.error(Messages.INSUF_HOLDINGS.value)
raise RuntimeError(Messages.INSUF_HOLDINGS.value)
return True
Expand Down
13 changes: 12 additions & 1 deletion src/Model/StockPriceGetter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from Utils.TaskThread import TaskThread
from Utils.ConfigurationManager import ConfigurationManager
from Utils.Utils import Markets


class StockPriceGetter(TaskThread):
Expand Down Expand Up @@ -58,11 +59,21 @@ def _fetch_price_data(self, symbol):

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

def convert_market_to_alphavantage(self, symbol):
"""
Convert the market (LSE, etc.) into the alphavantage market compatible string
i.e.: the LSE needs to be converted to LON
"""
# Extract the market part from the symbol string
market = str(symbol).split(':')[0]
av_market = Markets[market]
return '{}:{}'.format(av_market.value, str(symbol).split(':')[1])

def get_last_data(self):
return self.lastData

Expand Down
2 changes: 1 addition & 1 deletion src/TradingMate.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def _update_share_trading_view(self, updateHistory=False):
# Update the view
validity = True
for h in self.portfolio.get_holding_list():
self.view.update_share_trading_holding(h.get_symbol(), h.get_amount(), h.get_open_price(),
self.view.update_share_trading_holding(h.get_symbol(), h.get_quantity(), h.get_open_price(),
h.get_last_price(), h.get_cost(), h.get_value(), h.get_profit_loss(), h.get_profit_loss_perc(), h.get_last_price_valid())
validity = validity and h.get_last_price_valid()
self.view.update_share_trading_portfolio_balances(
Expand Down
34 changes: 17 additions & 17 deletions src/UI/AddTradeDialogWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def create_UI(self):
ttk.Label(self, text="Action:").grid(row=1, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Market:").grid(row=2, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Symbol:").grid(row=3, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Amount:").grid(row=4, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Quantity:").grid(row=4, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Price [p] :").grid(row=5, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Fee [£] :").grid(row=6, sticky="w", padx=5, pady=5)
ttk.Label(self, text="Stamp Duty [%] :").grid(row=7, sticky="w", padx=5, pady=5)
Expand Down Expand Up @@ -64,10 +64,10 @@ def create_UI(self):
self.eSymbol = ttk.Entry(self, textvariable=self.symbolSelected)
self.eSymbol.grid(row=3, column=1, sticky="w", padx=5, pady=5)

self.amountSelected = tk.StringVar()
self.amountSelected.trace_add('write', self.check_data_validity)
self.eAmount = ttk.Entry(self, textvariable=self.amountSelected)
self.eAmount.grid(row=4, column=1, sticky="w", padx=5, pady=5)
self.quantity_selected = tk.StringVar()
self.quantity_selected.trace_add('write', self.check_data_validity)
self.e_quantity = ttk.Entry(self, textvariable=self.quantity_selected)
self.e_quantity.grid(row=4, column=1, sticky="w", padx=5, pady=5)

self.priceSelected = tk.StringVar()
self.priceSelected.trace_add('write', self.check_data_validity)
Expand Down Expand Up @@ -99,43 +99,43 @@ def on_market_selected(self, selection):
def on_action_selected(self, selection):
# Clear data entry
self.symbolSelected.set("")
self.amountSelected.set("")
self.quantity_selected.set("")
self.priceSelected.set("")
self.feeSelected.set("")
self.stampDutySelected.set("")
# Change layout
if selection == Actions.BUY.name:
self.eMarket.config(state='enabled')
self.eSymbol.config(state='enabled')
self.eAmount.config(state='enabled')
self.e_quantity.config(state='enabled')
self.ePrice.config(state='enabled')
self.eFee.config(state='enabled')
self.eStampDuty.config(state='enabled')
elif selection == Actions.SELL.name:
self.eMarket.config(state='enabled')
self.eSymbol.config(state='enabled')
self.eAmount.config(state='enabled')
self.e_quantity.config(state='enabled')
self.ePrice.config(state='enabled')
self.eFee.config(state='enabled')
self.eStampDuty.config(state='disabled')
elif selection == Actions.DEPOSIT.name:
self.eMarket.config(state='disabled')
self.eSymbol.config(state='disabled')
self.eAmount.config(state='enabled')
self.e_quantity.config(state='enabled')
self.ePrice.config(state='disabled')
self.eFee.config(state='disabled')
self.eStampDuty.config(state='disabled')
elif selection == Actions.DIVIDEND.name:
self.eMarket.config(state='enabled')
self.eSymbol.config(state='enabled')
self.eAmount.config(state='enabled')
self.e_quantity.config(state='enabled')
self.ePrice.config(state='disabled')
self.eFee.config(state='disabled')
self.eStampDuty.config(state='disabled')
elif selection == Actions.WITHDRAW.name:
self.eMarket.config(state='disabled')
self.eSymbol.config(state='disabled')
self.eAmount.config(state='enabled')
self.e_quantity.config(state='enabled')
self.ePrice.config(state='disabled')
self.eFee.config(state='disabled')
self.eStampDuty.config(state='disabled')
Expand All @@ -146,8 +146,8 @@ def add_new_trade(self):
item["date"] = self.dateSelected.get()
item["action"] = self.actionSelected.get()
market = Markets[self.marketSelected.get()]
item["symbol"] = (market.value + ":" + self.symbolSelected.get()) if self.symbolSelected.get() is not "" else ""
item["amount"] = float(self.amountSelected.get()) if self.amountSelected.get() is not "" else 0
item["symbol"] = (market.name + ":" + self.symbolSelected.get()) if self.symbolSelected.get() is not "" else ""
item["quantity"] = float(self.quantity_selected.get()) if self.quantity_selected.get() is not "" else 0
item["price"] = float(self.priceSelected.get()) if self.priceSelected.get() is not "" else 0
item["fee"] = float(self.feeSelected.get()) if self.feeSelected.get() is not "" else 0
item["stamp_duty"] = float(self.stampDutySelected.get()) if self.stampDutySelected.get() is not "" else 0
Expand All @@ -161,7 +161,7 @@ def add_new_trade(self):

def check_data_validity(self, *args):
# Check the validity of the Entry widgets data to enable the Add button
valid = self.is_date_valid() and self.is_symbol_valid() and self.is_amount_valid() \
valid = self.is_date_valid() and self.is_symbol_valid() and self.is_quantity_valid() \
and self.is_price_valid() and self.is_fee_valid() and self.is_sd_valid()
self.addButton.config(state="normal" if valid else "disabled")

Expand All @@ -183,12 +183,12 @@ def is_symbol_valid(self):
self.symbolSelected.set(self.symbolSelected.get().upper())
return True

def is_amount_valid(self):
def is_quantity_valid(self):
# If widget is disabled it should not affect the overall validity
if str(self.eAmount.cget("state")) == tk.DISABLED:
if str(self.e_quantity.cget("state")) == tk.DISABLED:
return True
try:
value = float(self.amountSelected.get())
value = float(self.quantity_selected.get())
if value > 0:
return True
return False
Expand Down
46 changes: 46 additions & 0 deletions src/UI/ConfirmWindow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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)

class ConfirmWindow(tk.Toplevel):

def __init__(self, master, title, message, ok_callback=None, cancel_callback=None):
tk.Toplevel.__init__(self)
self.parent = master
self.message = message
self.ok_callback = ok_callback
self.cancel_callback = cancel_callback
self.transient(self.parent)
self.title(title)
self.geometry("+%d+%d" % (self.parent.winfo_rootx()+10, self.parent.winfo_rooty()+10))
self.protocol("WM_DELETE_WINDOW", self.destroy)
self.grab_set()
self.focus_set()

self.create_UI()

def create_UI(self):
ttk.Label(self, text=self.message).grid(row=0, sticky="w", padx=5, pady=5)
ok_button = ttk.Button(self, text="Ok", command=self.ok)
ok_button.grid(row=1, column=1, sticky="w", padx=5, pady=5)
cancel_button = ttk.Button(self, text="Cancel", command=self.cancel)
cancel_button.grid(row=1, column=2, sticky="e", padx=5, pady=5)

# Make the mas ter thread block execution until this window is closed
self.parent.wait_window(self)

def ok(self):
if self.ok_callback is not None:
self.ok_callback()
self.destroy()

def cancel(self):
if self.cancel_callback is not None:
self.cancel_callback()
self.destroy()

0 comments on commit 38cf93c

Please sign in to comment.