complete db & graph implementation (except for delete)

This commit is contained in:
Roland W-H 2025-04-24 22:55:26 +01:00
parent d8c4b5d64d
commit 929c3719c0
12 changed files with 396 additions and 244 deletions

Binary file not shown.

View File

@ -1,55 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>PlatformRename</class> <class>PlatformRename</class>
<widget class="QWidget" name="PlatformRename"> <widget class="QDialog" name="PlatformRename">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>300</width> <width>300</width>
<height>90</height> <height>100</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Rename Platform</string> <string>Name Platform</string>
</property> </property>
<widget class="QPushButton" name="rename_plat_ok_but"> <widget class="QLineEdit" name="rename_plat_box">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>221</x> <x>7</x>
<y>62</y> <y>41</y>
<width>75</width> <width>287</width>
<height>24</height> <height>22</height>
</rect> </rect>
</property> </property>
<property name="text"> <property name="font">
<string>OK</string> <font>
<pointsize>11</pointsize>
</font>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="rename_plat_lab"> <widget class="QLabel" name="rename_plat_lab">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>60</x> <x>35</x>
<y>10</y> <y>9</y>
<width>191</width> <width>241</width>
<height>20</height> <height>20</height>
</rect> </rect>
</property> </property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text"> <property name="text">
<string>Enter a new name for the platform</string> <string>Enter a new name for the platform</string>
</property> </property>
</widget> </widget>
<widget class="QLineEdit" name="rename_plat_box"> <widget class="QPushButton" name="rename_plat_ok_but">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>8</x> <x>220</x>
<y>34</y> <y>70</y>
<width>287</width> <width>75</width>
<height>22</height> <height>24</height>
</rect> </rect>
</property> </property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>OK</string>
</property>
</widget> </widget>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections>
<connection>
<sender>rename_plat_ok_but</sender>
<signal>clicked()</signal>
<receiver>PlatformRename</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>71</y>
</hint>
<hint type="destinationlabel">
<x>149</x>
<y>44</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -1,104 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>ResultsWindow</class> <class>OutputWindow</class>
<widget class="QWidget" name="ResultsWindow"> <widget class="QWidget" name="OutputWindow">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>1330</width>
<height>355</height> <height>630</height>
</rect> </rect>
</property> </property>
<property name="minimumSize">
<size>
<width>400</width>
<height>355</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>355</height>
</size>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Results</string> <string>Results</string>
</property> </property>
<widget class="QTextEdit" name="output"> <widget class="MplWidget" name="graphWidget" native="true">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>-10</x>
<y>10</y> <y>-10</y>
<width>381</width> <width>1350</width>
<height>301</height> <height>650</height>
</rect> </rect>
</property> </property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
</widget>
<widget class="QPushButton" name="res_ok_but">
<property name="geometry">
<rect>
<x>318</x>
<y>323</y>
<width>75</width>
<height>24</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
<widget class="QPushButton" name="res_save_but">
<property name="geometry">
<rect>
<x>238</x>
<y>323</y>
<width>75</width>
<height>24</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Save</string>
</property>
</widget> </widget>
</widget> </widget>
<tabstops> <customwidgets>
<tabstop>output</tabstop> <customwidget>
<tabstop>res_save_but</tabstop> <class>MplWidget</class>
<tabstop>res_ok_but</tabstop> <extends>QWidget</extends>
</tabstops> <header>widgets/mpl_widget</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections/>
<connection>
<sender>res_ok_but</sender>
<signal>clicked(bool)</signal>
<receiver>ResultsWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>357</x>
<y>281</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -2,6 +2,9 @@
<ui version="4.0"> <ui version="4.0">
<class>PlatformEdit</class> <class>PlatformEdit</class>
<widget class="QWidget" name="PlatformEdit"> <widget class="QWidget" name="PlatformEdit">
<property name="windowModality">
<enum>Qt::WindowModality::ApplicationModal</enum>
</property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>

View File

@ -2,6 +2,9 @@
<ui version="4.0"> <ui version="4.0">
<class>PlatformList</class> <class>PlatformList</class>
<widget class="QWidget" name="PlatformList"> <widget class="QWidget" name="PlatformList">
<property name="windowModality">
<enum>Qt::WindowModality::ApplicationModal</enum>
</property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
@ -120,5 +123,22 @@
</widget> </widget>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections>
<connection>
<sender>plist_save_but</sender>
<signal>clicked()</signal>
<receiver>PlatformList</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>196</x>
<y>453</y>
</hint>
<hint type="destinationlabel">
<x>131</x>
<y>236</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -1,11 +1,13 @@
import os import os
import sqlite3 import sqlite3
import data_struct
import resource_finder
from data_struct import Platform from data_struct import Platform
class DBHandler: class DBHandler:
def __init__(self): def __init__(self):
# Function to create all necessary database tables if the DB file doesn't exist
def create_tables(): def create_tables():
self.cur.execute(""" self.cur.execute("""
CREATE TABLE "tblPlatforms" ( CREATE TABLE "tblPlatforms" (
@ -59,16 +61,17 @@ class DBHandler:
) )
""") """)
if not os.path.exists("SIPPCompare.db"): if not os.path.exists(resource_finder.get_res_path("SIPPCompare.db")):
db_exists = False db_exists = False
else: else:
db_exists = True db_exists = True
self.conn = sqlite3.connect("SIPPCompare.db") self.conn = sqlite3.connect(resource_finder.get_res_path("SIPPCompare.db"))
self.cur = self.conn.cursor() self.cur = self.conn.cursor()
if not db_exists: if not db_exists:
create_tables() create_tables()
# Retrieve the list of platform names for list population
def retrieve_plat_list(self) -> list: def retrieve_plat_list(self) -> list:
res = self.cur.execute("SELECT PlatformName FROM tblPlatforms").fetchall() res = self.cur.execute("SELECT PlatformName FROM tblPlatforms").fetchall()
plat_name_list = [] plat_name_list = []
@ -77,7 +80,80 @@ class DBHandler:
return plat_name_list return plat_name_list
def write_platforms(self, plat_list: list[Platform]):
for i in range(len(plat_list)):
platforms_data = [
plat_list[i].plat_id,
plat_list[i].plat_name,
plat_list[i].enabled
]
flat_plat_fees_data = [
plat_list[i].plat_id,
plat_list[i].share_plat_fee,
plat_list[i].share_plat_max_fee
]
flat_deal_fees_data = [
plat_list[i].plat_id,
plat_list[i].fund_deal_fee,
plat_list[i].share_deal_fee,
plat_list[i].share_deal_reduce_trades,
plat_list[i].share_deal_reduce_amount
]
res = self.cur.execute(f"""
SELECT EXISTS(
SELECT PlatformID FROM tblPlatforms
WHERE PlatformID = {i}
)""").fetchall()
if res[0][0] == 1:
self.cur.execute(f"""
UPDATE tblPlatforms SET
PlatformID = ?,
PlatformName = ?,
IsEnabled = ?
WHERE PlatformID = {i}
""", platforms_data)
self.cur.execute(f"""
UPDATE tblFlatPlatFees SET
PlatformID = ?,
SharePlatFee = ?,
SharePlatMaxFee = ?
WHERE PlatformID = {i}
""", flat_plat_fees_data)
self.cur.execute(f"""
UPDATE tblFlatDealFees SET
PlatformID = ?,
FundDealFee = ?,
ShareDealFee = ?,
ShareDealReduceTrades = ?,
ShareDealReduceAmount = ?
WHERE PlatformID = {i}
""", flat_deal_fees_data)
self.cur.execute(f"DELETE FROM tblFundPlatFee WHERE PlatformID = {i}")
else:
self.cur.execute("INSERT INTO tblPlatforms VALUES (?, ?, ?)", platforms_data)
self.cur.execute("INSERT INTO tblFlatPlatFees VALUES (?, ?, ?)", flat_plat_fees_data)
self.cur.execute("INSERT INTO tblFlatDealFees VALUES (?, ?, ?, ?, ?)", flat_deal_fees_data)
exec_str = f"INSERT INTO tblFundPlatFee VALUES\n"
for x in range(len(plat_list[i].fund_plat_fee[0])):
band = plat_list[i].fund_plat_fee[0][x]
fee = plat_list[i].fund_plat_fee[1][x]
exec_str += f"({i}, {band}, {fee}),\n"
exec_str = exec_str[:-2]
self.cur.execute(exec_str)
self.conn.commit()
# Retrieve all info about all platforms in DB and initialise Platform objects
def retrieve_platforms(self) -> list[Platform]: def retrieve_platforms(self) -> list[Platform]:
# Retrieve all one-to-one relations
platforms_res = self.cur.execute(""" platforms_res = self.cur.execute("""
SELECT SELECT
-- tblPlatforms -- tblPlatforms
@ -104,10 +180,13 @@ class DBHandler:
platform[5], platform[6], platform[7], platform[8]] platform[5], platform[6], platform[7], platform[8]]
platforms.append(this_platform) platforms.append(this_platform)
# Insert 2D array into each platform data in preparation for fund_plat_fee retrival
for platform in platforms: for platform in platforms:
platform.insert(1, [[], []]) platform.insert(1, [[], []])
fund_plat_fee_res = self.cur.execute("SELECT * FROM tblFundPlatFee").fetchall() # Get all records from tblFundPlatFee, add them to the platforms list based on ID
# WARNING: This code is dependent on PlatformID being sequential from 0 in DB records
fund_plat_fee_res = self.cur.execute("SELECT * FROM tblFundPlatFee ORDER BY PlatformID ASC").fetchall()
for i in range(len(fund_plat_fee_res)): for i in range(len(fund_plat_fee_res)):
plat_id = fund_plat_fee_res[i][0] plat_id = fund_plat_fee_res[i][0]
platforms[plat_id][1][0].append(fund_plat_fee_res[i][1]) platforms[plat_id][1][0].append(fund_plat_fee_res[i][1])
@ -123,15 +202,18 @@ class DBHandler:
return platform_obj_list return platform_obj_list
# This function writes the details the user entered this session to the DB
def write_user_details(self, pension_val: float, slider_val: int, share_trades: int, fund_trades: int): def write_user_details(self, pension_val: float, slider_val: int, share_trades: int, fund_trades: int):
# Hardcode UserID as 0 # Hardcode UserID as 0
user_details_data = (0, pension_val, slider_val, share_trades, fund_trades) user_details_data = (0, pension_val, slider_val, share_trades, fund_trades)
# Check if there is already a record in tblUserDetails
res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone() res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone()
if res[0] == 0: if res[0] == 0:
# If there isn't then insert a new record
self.cur.execute("INSERT INTO tblUserDetails VALUES (?, ?, ?, ?, ?)", user_details_data) self.cur.execute("INSERT INTO tblUserDetails VALUES (?, ?, ?, ?, ?)", user_details_data)
else: else:
# If there is then update the existing record (only ever one record as of now)
self.cur.execute(""" self.cur.execute("""
UPDATE tblUserDetails SET UPDATE tblUserDetails SET
UserID = ?, UserID = ?,
@ -142,6 +224,7 @@ class DBHandler:
""", user_details_data) """, user_details_data)
self.conn.commit() self.conn.commit()
# Function to retrieve details entered by the user in prev session from DB
def retrieve_user_details(self) -> dict: def retrieve_user_details(self) -> dict:
res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone() res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone()
if res[0] == 0: if res[0] == 0:

View File

@ -1,23 +1,12 @@
from PyQt6.QtWidgets import QApplication
import sys import sys
import platform_edit from PyQt6.QtWidgets import QApplication
import main_window
from platform_edit import PlatformEdit
from main_window import SIPPCompare
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Show platform edit window first, before main win window = SIPPCompare()
# When debugging, can be useful to autofill values to save time
"""if len(sys.argv) > 1:
if sys.argv[1] == "--DEBUG_AUTOFILL":
window = platform_edit.PlatformEdit(True)
else:
window = platform_edit.PlatformEdit(False)
else:
window = platform_edit.PlatformEdit(False)"""
#plat_edit_win = platform_edit.PlatformEdit()
window = main_window.SIPPCompare()
window.show() window.show()
app.exec() app.exec()

View File

@ -1,17 +1,18 @@
from PyQt6.QtGui import QIntValidator, QIcon
from PyQt6.QtWidgets import QMainWindow, QWidget
from PyQt6 import uic from PyQt6 import uic
from PyQt6.QtGui import QIntValidator, QIcon
from PyQt6.QtWidgets import QMainWindow
import output_window
import platform_list
import resource_finder import resource_finder
import db_handler from db_handler import DBHandler
from output_window import OutputWindow
from platform_list import PlatformList
class SIPPCompare(QMainWindow): class SIPPCompare(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# Import Qt Designer UI XML file # Import Qt Designer UI XML file
uic.loadUi(resource_finder.get_res_path("gui/main_gui.ui"), self) uic.loadUi(resource_finder.get_res_path("gui/main_gui.ui"), self)
self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
@ -32,11 +33,12 @@ class SIPPCompare(QMainWindow):
self.fund_deal_fees = 0.0 self.fund_deal_fees = 0.0
self.share_plat_fees = 0.0 self.share_plat_fees = 0.0
self.share_deal_fees = 0.0 self.share_deal_fees = 0.0
self.results = []
# Create window objects # Create window objects
self.db = db_handler.DBHandler() self.db = DBHandler()
self.platform_list_win = platform_list.PlatformList(self.db) self.platform_list_win = PlatformList(self.db)
self.output_win = output_window.OutputWindow() self.output_win = OutputWindow()
# Handle events # Handle events
self.calc_but.clicked.connect(self.calculate_fees) self.calc_but.clicked.connect(self.calculate_fees)
@ -61,8 +63,6 @@ class SIPPCompare(QMainWindow):
self.fund_trades_combo.setCurrentText(str(prev_session_data["fund_trades"])) self.fund_trades_combo.setCurrentText(str(prev_session_data["fund_trades"]))
self.calc_but.setFocus() self.calc_but.setFocus()
# Display slider position as mix between two nums (funds/shares) # Display slider position as mix between two nums (funds/shares)
def update_slider_lab(self): def update_slider_lab(self):
slider_val = self.mix_slider.value() slider_val = self.mix_slider.value()
@ -78,6 +78,7 @@ class SIPPCompare(QMainWindow):
self.calc_but.setEnabled(False) self.calc_but.setEnabled(False)
# Get variables from platform editor input fields # Get variables from platform editor input fields
"""
def init_variables(self): def init_variables(self):
self.optional_boxes = self.platform_win.get_optional_boxes() self.optional_boxes = self.platform_win.get_optional_boxes()
self.fund_plat_fee = self.platform_win.get_fund_plat_fee() self.fund_plat_fee = self.platform_win.get_fund_plat_fee()
@ -109,50 +110,59 @@ class SIPPCompare(QMainWindow):
self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount() self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount()
else: else:
self.share_deal_reduce_amount = None self.share_deal_reduce_amount = None
"""
def init_variables(self):
self.fund_plat_fee = 1
# Calculate fees # Calculate fees
def calculate_fees(self): def calculate_fees(self):
self.init_variables() # Set to empty list each time to avoid persistence
self.results = []
# Set to zero each time to avoid persistence
self.fund_plat_fees = 0
# Get user input # Get user input
value_num = float(self.value_input.value()) value_num = float(self.value_input.value())
slider_val: int = self.mix_slider.value() slider_val: int = self.mix_slider.value()
fund_trades_num = int(self.fund_trades_combo.currentText()) fund_trades_num = int(self.fund_trades_combo.currentText())
share_trades_num = int(self.share_trades_combo.currentText()) share_trades_num = int(self.share_trades_combo.currentText())
# Funds/shares mix
funds_value = (slider_val / 100) * value_num funds_value = (slider_val / 100) * value_num
if self.fund_deal_fee is not None:
self.fund_deal_fees = fund_trades_num * self.fund_deal_fee
for i in range(1, len(self.fund_plat_fee[0])):
band = self.fund_plat_fee[0][i]
prev_band = self.fund_plat_fee[0][i - 1]
fee = self.fund_plat_fee[1][i]
gap = (band - prev_band)
if funds_value > gap:
self.fund_plat_fees += gap * (fee / 100)
funds_value -= gap
else:
self.fund_plat_fees += funds_value * (fee / 100)
break
shares_value = (1 - (slider_val / 100)) * value_num shares_value = (1 - (slider_val / 100)) * value_num
if self.share_plat_max_fee is not None:
if (self.share_plat_fee * shares_value / 12) > self.share_plat_max_fee:
self.share_plat_fees = self.share_plat_max_fee * 12
else:
self.share_plat_fees = self.share_plat_fee * shares_value
if self.share_deal_reduce_trades is not None: for platform in self.platform_list_win.plat_list:
if (share_trades_num / 12) >= self.share_deal_reduce_trades: fund_plat_fees = 0.0
self.share_deal_fees = self.share_deal_reduce_amount * share_trades_num fund_deal_fees = 0.0
else: share_plat_fees = 0.0
self.share_deal_fees = self.share_deal_fee * share_trades_num share_deal_fees = 0.0
plat_name = platform.plat_name
if platform.fund_deal_fee is not None:
fund_deal_fees = fund_trades_num * platform.fund_deal_fee
for i in range(1, len(platform.fund_plat_fee[0])):
band = platform.fund_plat_fee[0][i]
prev_band = platform.fund_plat_fee[0][i - 1]
fee = platform.fund_plat_fee[1][i]
gap = (band - prev_band)
if funds_value > gap:
fund_plat_fees += gap * (fee / 100)
funds_value -= gap
else:
fund_plat_fees += funds_value * (fee / 100)
break
if platform.share_plat_max_fee is not None:
if (platform.share_plat_fee * shares_value / 12) > platform.share_plat_max_fee:
share_plat_fees = platform.share_plat_max_fee * 12
else:
share_plat_fees = platform.share_plat_fee * shares_value
if platform.share_deal_reduce_trades is not None:
if (share_trades_num / 12) >= platform.share_deal_reduce_trades:
share_deal_fees = platform.share_deal_reduce_amount * share_trades_num
else:
share_deal_fees = platform.share_deal_fee * share_trades_num
self.results.append([fund_plat_fees, fund_deal_fees, share_plat_fees, share_deal_fees, plat_name])
self.db.write_user_details(value_num, slider_val, share_trades_num, fund_trades_num) self.db.write_user_details(value_num, slider_val, share_trades_num, fund_trades_num)
self.show_output_win() self.show_output_win()
@ -160,10 +170,7 @@ class SIPPCompare(QMainWindow):
# Show the output window - this func is called from calculate_fee() # Show the output window - this func is called from calculate_fee()
def show_output_win(self): def show_output_win(self):
# Refresh the results when new fees are calculated # Refresh the results when new fees are calculated
self.output_win.display_output(self.fund_plat_fees, self.fund_deal_fees, self.output_win.display_output(self.results)
self.share_plat_fees, self.share_deal_fees,
self.plat_name
)
self.output_win.show() self.output_win.show()
def show_platform_list(self): def show_platform_list(self):

View File

@ -1,10 +1,10 @@
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QWidget
from PyQt6 import uic
import datetime import datetime
import os import os
from PyQt6 import uic
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QWidget
import resource_finder import resource_finder
@ -15,6 +15,7 @@ class OutputWindow(QWidget):
uic.loadUi(resource_finder.get_res_path("gui/output_window.ui"), self) uic.loadUi(resource_finder.get_res_path("gui/output_window.ui"), self)
self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
"""
# Initialise class variables # Initialise class variables
self.results_str = "" self.results_str = ""
self.platform_name = "" self.platform_name = ""
@ -62,3 +63,25 @@ class OutputWindow(QWidget):
self.results_str += f"\n\nTotal fees: £{round(total_fees, 2):.2f}" self.results_str += f"\n\nTotal fees: £{round(total_fees, 2):.2f}"
self.output.setText(self.results_str) self.output.setText(self.results_str)
"""
def display_output(self, results: list):
self.graphWidget.canvas.axes.clear()
self.graphWidget.canvas.axes.cla()
self.graphWidget.canvas.draw_idle()
ax = self.graphWidget.canvas.axes
#self.graphWidget.clf()
names = []
values = []
for result in results:
names.append(result[4])
values.append(sum(result[:4]))
h_bars = ax.barh(names, values)
#labels = []
#for value in values:
# labels.append(f"£{str(value)}")
ax.bar_label(h_bars, label_type='center', labels=[f"£{x:,.2f}" for x in h_bars.datavalues])
#ax.draw()
#self.graphWidget.draw()

View File

@ -1,12 +1,12 @@
from PyQt6.QtCore import QRegularExpression from PyQt6 import uic
from PyQt6.QtCore import QRegularExpression, QRect
from PyQt6.QtGui import QRegularExpressionValidator, QFont, QIcon from PyQt6.QtGui import QRegularExpressionValidator, QFont, QIcon
from PyQt6.QtWidgets import QWidget, QLabel from PyQt6.QtWidgets import QWidget, QLabel
from PyQt6 import uic
from widgets.fastedit_spinbox import FastEditQDoubleSpinBox
from data_struct import Platform
import main_window
import resource_finder import resource_finder
from db_handler import DBHandler
from data_struct import Platform
from widgets.fastedit_spinbox import FastEditQDoubleSpinBox
class PlatformEdit(QWidget): class PlatformEdit(QWidget):
@ -20,14 +20,10 @@ class PlatformEdit(QWidget):
self.plat = plat self.plat = plat
self.fund_plat_fee = self.plat.fund_plat_fee self.fund_plat_fee = self.plat.fund_plat_fee
self.widgets_list_list = [] self.widgets_list_list = []
if len(self.plat.fund_plat_fee[0]) > 1:
self.fund_fee_rows = len(self.plat.fund_plat_fee[0]) self.fund_fee_rows = len(self.plat.fund_plat_fee[0]) - 1
""" else:
# Debugging feature: set with "--DEBUG_AUTOFILL" cmd argument self.fund_fee_rows = 1
self.autofill = autofill
if autofill:
self.save_but.setEnabled(True)
"""
self.required_fields = [ self.required_fields = [
self.share_plat_fee_box, self.share_plat_fee_box,
@ -58,6 +54,7 @@ class PlatformEdit(QWidget):
False False
] ]
# Set optional checkboxes based on DB storage
if self.plat.plat_name is None: if self.plat.plat_name is None:
self.check_boxes_ticked[0] = False self.check_boxes_ticked[0] = False
self.plat_name_check.setChecked(False) self.plat_name_check.setChecked(False)
@ -102,8 +99,10 @@ class PlatformEdit(QWidget):
self.share_deal_reduce_amount_check.setChecked(True) self.share_deal_reduce_amount_check.setChecked(True)
self.share_deal_reduce_amount_box.setValue(self.plat.share_deal_reduce_amount) self.share_deal_reduce_amount_box.setValue(self.plat.share_deal_reduce_amount)
self.first_tier_box.setValue(self.plat.fund_plat_fee[0][1]) # Populate fund platform fee rows from DB
self.first_tier_fee_box.setValue(self.plat.fund_plat_fee[1][1]) if len(self.plat.fund_plat_fee[0]) > 1:
self.first_tier_box.setValue(self.plat.fund_plat_fee[0][1])
self.first_tier_fee_box.setValue(self.plat.fund_plat_fee[1][1])
self.add_row(loading=True) self.add_row(loading=True)
# Handle events # Handle events
@ -150,34 +149,34 @@ class PlatformEdit(QWidget):
# Get fee structure variables from user input when "Save" clicked # Get fee structure variables from user input when "Save" clicked
def init_variables(self): def init_variables(self):
""" self.plat.fund_plat_fee = self.create_plat_fee_struct()
# If debugging, save time by hardcoding self.plat.share_plat_fee = float(self.share_plat_fee_box.value()) / 100
if self.autofill: self.plat.share_deal_fee = float(self.share_deal_fee_box.value())
self.plat_name = "AJBell"
self.fund_plat_fee = [
[0, 250000, 1000000, 2000000],
[0, 0.25, 0.1, 0.05]
]
self.fund_deal_fee = 1.50
self.share_plat_fee = 0.0025
self.share_plat_max_fee = 3.50
self.share_deal_fee = 5.00
self.share_deal_reduce_trades = 10
self.share_deal_reduce_amount = 3.50
self.check_boxes_ticked = [True, True, True, True, True]
else:
"""
self.plat_name = self.plat_name_box.text()
self.fund_plat_fee = self.create_plat_fee_struct()
self.fund_deal_fee = float(self.fund_deal_fee_box.value())
self.share_plat_fee = float(self.share_plat_fee_box.value()) / 100
self.share_plat_max_fee = float(self.share_plat_max_fee_box.value())
self.share_deal_fee = float(self.share_deal_fee_box.value())
self.share_deal_reduce_trades = float(self.share_deal_reduce_trades_box.value())
self.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.value())
# Once user input is received show main window if self.check_boxes_ticked[0]:
self.main_win.show() self.plat.plat_name = self.plat_name_box.text()
else:
self.plat.plat_name = None
if self.check_boxes_ticked[1]:
self.plat.fund_deal_fee = float(self.fund_deal_fee_box.value())
else:
self.plat.fund_deal_fee = None
if self.check_boxes_ticked[2]:
self.plat.share_plat_max_fee = float(self.share_plat_max_fee_box.value())
else:
self.plat.share_plat_max_fee = None
if self.check_boxes_ticked[3]:
self.plat.share_deal_reduce_trades = int(self.share_deal_reduce_trades_box.value())
else:
self.plat.share_deal_reduce_trades = None
if self.check_boxes_ticked[4]:
self.plat.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.value())
else:
self.plat.share_deal_reduce_amount = None
# This method does multiple things in order to validate the user's inputs: # This method does multiple things in order to validate the user's inputs:
# 1) Check all required fields have a non-zero value # 1) Check all required fields have a non-zero value
@ -261,12 +260,12 @@ class PlatformEdit(QWidget):
def add_row(self, loading: bool = False): def add_row(self, loading: bool = False):
if loading: if loading:
rows_needed = self.fund_fee_rows rows_needed = self.fund_fee_rows - 1
else: else:
rows_needed = 1 rows_needed = 1
widgets = []
for x in range(rows_needed): for x in range(rows_needed):
widgets = []
font = QFont() font = QFont()
font.setPointSize(11) font.setPointSize(11)
@ -279,7 +278,7 @@ class PlatformEdit(QWidget):
widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
widgets[1].setFont(font) widgets[1].setFont(font)
if loading: if loading:
widgets[1].setValue(self.plat.fund_plat_fee[0][x+1]) widgets[1].setValue(self.plat.fund_plat_fee[0][x+2])
widgets[1].valueChanged.connect(self.check_valid) widgets[1].valueChanged.connect(self.check_valid)
widgets[1].valueChanged.connect(self.update_tier_labels) widgets[1].valueChanged.connect(self.update_tier_labels)
@ -293,13 +292,20 @@ class PlatformEdit(QWidget):
widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
widgets[3].setFont(font) widgets[3].setFont(font)
if loading: if loading:
widgets[3].setValue(self.plat.fund_plat_fee[1][x+1]) widgets[3].setValue(self.plat.fund_plat_fee[1][x+2])
widgets[3].valueChanged.connect(self.check_valid) widgets[3].valueChanged.connect(self.check_valid)
# TODO: why 28.5? # TODO: why 28.5?
self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * (self.fund_fee_rows + 1), 0))) if loading:
grid_height = int(round(28.5 * self.fund_fee_rows))
else:
grid_height = int(round(28.5 * (self.fund_fee_rows + 1)))
self.gridLayoutWidget_2.setGeometry(QRect(11, 309, 611, grid_height))
for i in range(len(widgets)): for i in range(len(widgets)):
self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1) if loading:
self.gridLayout_2.addWidget(widgets[i], x + 1, i, 1, 1)
else:
self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1)
if not loading: if not loading:
self.fund_fee_rows += 1 self.fund_fee_rows += 1
@ -324,6 +330,7 @@ class PlatformEdit(QWidget):
self.add_row_but.setEnabled(False) self.add_row_but.setEnabled(False)
self.check_valid() self.check_valid()
self.update_tier_labels()
# TODO: Tab order # TODO: Tab order

View File

@ -1,56 +1,94 @@
from PyQt6.QtWidgets import QWidget, QListWidgetItem
from PyQt6.QtGui import QIcon, QRegularExpressionValidator
from PyQt6.QtCore import QRegularExpression
from PyQt6 import uic from PyQt6 import uic
from PyQt6.QtCore import QRegularExpression
from PyQt6.QtGui import QIcon, QRegularExpressionValidator
from PyQt6.QtWidgets import QWidget, QListWidgetItem, QDialog
import resource_finder import resource_finder
import data_struct from db_handler import DBHandler
import platform_edit from data_struct import Platform
from platform_edit import PlatformEdit
class PlatformRename(QWidget): class PlatformRename(QDialog):
def __init__(self): def __init__(self, parent=None):
super().__init__() super().__init__(parent)
# Import Qt Designer UI XML file # Import Qt Designer UI XML file
uic.loadUi(resource_finder.get_res_path("gui/dialogs/platform_rename.ui"), self) uic.loadUi(resource_finder.get_res_path("gui/dialogs/platform_rename.ui"), self)
self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
self.rename_plat_box.setFocus()
self.new_name = ""
# Set validators # Set validators
# Regex accepts any characters that match [a-Z], [0-9] or _ # Regex accepts any characters that match [a-Z], [0-9] or _
self.rename_plat_box.setValidator( self.rename_plat_box.setValidator(
QRegularExpressionValidator(QRegularExpression("\\w*")) QRegularExpressionValidator(QRegularExpression("\\w*"))
) )
self.rename_plat_ok_but.clicked.connect(self.store_new_name)
def store_new_name(self):
self.new_name = self.rename_plat_box.text()
def closeEvent(self, event):
event.ignore()
self.reject()
class PlatformList(QWidget): class PlatformList(QWidget):
def __init__(self, db): def __init__(self, db: DBHandler):
super().__init__() super().__init__()
# Import Qt Designer UI XML file # Import Qt Designer UI XML file
uic.loadUi(resource_finder.get_res_path("gui/platform_list.ui"), self) uic.loadUi(resource_finder.get_res_path("gui/platform_list.ui"), self)
self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico"))) self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
self.plat_list_dialog = PlatformRename()
self.p_edit = None
self.db = db self.db = db
self.plat_name_list = self.db.retrieve_plat_list() self.plat_edit_win = None
self.plat_list = self.db.retrieve_platforms() self.plat_list_dialog = PlatformRename()
print(self.plat_list[1].fund_plat_fee) self.plat_list = []
print(self.plat_name_list) self.plat_name_list = []
self.new_plat_name = ""
self.update_plat_list()
for platform in self.plat_name_list: for i in range(len(self.plat_name_list)):
plat_name = self.plat_name_list[i]
item = QListWidgetItem() item = QListWidgetItem()
item.setText(platform) if plat_name is not None:
item.setText(plat_name)
else:
item.setText(f"Unnamed [ID: {i}]")
self.platListWidget.addItem(item) self.platListWidget.addItem(item)
# Handle events # Handle events
self.add_plat_but.clicked.connect(self.add_platform) self.add_plat_but.clicked.connect(self.add_platform)
self.del_plat_but.clicked.connect(self.remove_platform) self.del_plat_but.clicked.connect(self.remove_platform)
self.edit_plat_but.clicked.connect(self.edit_platform) self.edit_plat_but.clicked.connect(self.edit_platform)
self.plist_save_but.clicked.connect(self.save_platforms)
self.plat_enabled_check.checkStateChanged.connect(self.toggle_platform_state) self.plat_enabled_check.checkStateChanged.connect(self.toggle_platform_state)
self.platListWidget.currentRowChanged.connect(self.get_enabled_state) self.platListWidget.currentRowChanged.connect(self.get_enabled_state)
def update_plat_list(self):
self.plat_name_list = self.db.retrieve_plat_list()
self.plat_list = self.db.retrieve_platforms()
def add_platform(self): def add_platform(self):
self.plat_list_dialog.show() name_dialog_res = self.plat_list_dialog.exec()
if name_dialog_res == QDialog.DialogCode.Accepted:
name = self.plat_list_dialog.new_name
index = self.platListWidget.count()
if name != "":
self.platListWidget.addItem(name)
name_param = name
else:
self.platListWidget.addItem(f"Unnamed [ID: {index}]")
name_param = None
self.plat_list.append(Platform(
index, [[0], [0]], name_param, True, 0, 0, None, 0, None, None)
)
self.plat_edit_win = PlatformEdit(self.plat_list[index])
self.plat_edit_win.show()
def get_enabled_state(self): def get_enabled_state(self):
index = self.platListWidget.currentRow() index = self.platListWidget.currentRow()
@ -62,8 +100,11 @@ class PlatformList(QWidget):
def edit_platform(self): def edit_platform(self):
index = self.platListWidget.currentRow() index = self.platListWidget.currentRow()
self.p_edit = platform_edit.PlatformEdit(self.plat_list[index]) self.plat_edit_win = PlatformEdit(self.plat_list[index])
self.p_edit.show() self.plat_edit_win.show()
def save_platforms(self):
self.db.write_platforms(self.plat_list)
def toggle_platform_state(self): def toggle_platform_state(self):
return None return None

14
src/widgets/mpl_widget.py Normal file
View File

@ -0,0 +1,14 @@
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from matplotlib.figure import Figure
from PyQt6.QtWidgets import QWidget, QVBoxLayout
class MplWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.canvas = FigureCanvasQTAgg(Figure(figsize=(10, 10), dpi=100))
vertical_layout = QVBoxLayout()
#vertical_layout.setSpacing(0)
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(1, 1, 1)
self.setLayout(vertical_layout)