上传日期:2024-02-01 00:00:01
上 传 者sh-1993
说明:  用于帮助在旧学校RuneScape中的Grand Exchange上进行销售的数据库工具
(A database tool used to help with merchanting on the Grand Exchange in Old School RuneScape)


# OSRS-GE-Ledger This project is a GUI that serves as a dashboard for viewing statistics of trades made through the Old School RuneScape (OSRS) Grand Exchange (GE), which is an ingame platform used for trading items within OSRS. The initial idea was to make an administrative/accounting tool with a ledger of submitted trades that would keep track of item stock, profit, etc. After the initial idea was implemented, I found various extensions worth implementing, causing the project to grow well beyond its initial idea. Various parts of the project have been completely reworked/redesigned after learning new things, ranging from small changes to fundamental reworks (e.g. object-oriented gui design), which may have left somewhat random traces. # Modules ### GUI The GUI is implemented in an object-oriented style. Modules related to GUI implementation are; - gui, which is the main gui frame on which other frames are built. - gui_tab_inventory contains specific GUI implementations. Other gui_tab code was removed as most of their use was transferred to the inventory tab. - The tabs are more or less replaced by the button panel on the left in the inventory tab, of which most buttons alter the content of its listboxes. - gui_backend, which contains implementations for filling specific data structures like listboxes that are used in the gui. - gui_formats, which contains specific format implementations and backgroundcolor specifications used in specific GUI components - gui_graph, which contains implementations for implementing/displaying graphs using the tkinter library ### Globals Throughout the project a lot of hardcoded values are used by multiple modules. Almost all those values are defined in the following classes, so they can be accessed and modified through centralized modules, rather than redefining the same variable in multiple classes. - path contains all file and folder references, as well general I/O methods for listing, loading and saving pickle files. - global_values contains various hard-coded, static global variables - resources (deprecated) was the previous implementation of global_values, its contents have been mostly moved elsewhere ### Graphs graph-related modules contain implementations for generating graphs. - graphs contains the implementation of Graph classes, that serve as a framework for setting up plots and graphs in a streamlined fashion - data_visualization contains specific implementations for generating specific graphs using graph/plot classes defined in graphs - generate_leagues_graphs contains logic for generating graphs covering specific timespans of the Leagues events. It is defined as a separate module as it is only relevant while a leagues event is ongoing. ### Util Modules with the util affix contain relatively simple methods like specific conversions that are used across the project - ts_util: time-related methods - ge_util: grand-exchange/osrs related methods ### Format format modules contain implementations that specify how the given input should be formatted and tend to produce a string or a backgroundcolor rgb tuple, given the input - gui_formats contains formats that are specific to certain parts of the gui - str_formats contains formats that may be used in the GUI, but also tend to occur elsewhere ### Data structures Modules that define or interact with various data structures are listed here - setup_local_files contains logic for creating/updating local files. This can range from the empty sqlite databases to data structures that are designed to contain specific data that may require updating, like the list of item_ids. Methods found in this module are stand-alone and do not require other data structures for setup/updating - local_files contains logic on accessing specific files, which may prompt an update if specific conditions are met. - ledger contains the implementation of both the ledger and the inventory that is derived from the ledger. The ledger is described in more detail in the Ledger section below. - data_preprocessing contains implementations for update protocols that should occur right after a data transfer, as they are too time-consuming to execute during runtime. - data_transfer contains various implementations for importing/moving/archiving data - gui_backend contains various implementations that produce data used in the gui ### Executables These modules can be executed for performing certain tasks, like running the GUI or importing data from the Raspberry Pi; - main.pyw: executable for running the GUI - scheduled_data_transfer.pyw: executable for importing raspberry pi data - transaction_parser.py: executable for parsing transactions logged by the Exchange Logger plugin - runelite_reader.py: executable for parsing runelite json exports - generate_leagues_graphs.py: executable for generating price graphs covering intervals before, during and after all existing leagues events for a subset of items ### Other - filter contains implementations for filtering lists objects based on specific criteria - threaded_tasks contains class implementations designed to run specific threaded tasks - manual_scripts contains methods that produce results requiring manual interpretation (e.g. identifying a list of unused files) # GUI Design ## TkGrid As an alternative approach to setting column/row indices and spans for each widget, a TkGrid object can be created, while passing it a list of equally sized strings. Each character represents a widget in the grid defined by the list of strings. E.g. ['AABBBB', 'AACCCC'] represents a 6x2 grid in which A is 2x2 and B, C are both 4x1. Using the available space, the proportions of space are assigned to the widgets tagged with the characters (e.g. passing grid=TkGrid(['AABBBB', 'AACCCC']).xywh('A') to a GuiObject constructor will allocate the 2x2 space to that widget). Additionally, the '*' char is treated as empty space and the grid assumes widgets are rectangular. Another major benefit is that modifying the grid will automatically rescale other widgets that are involved in this grid as well, without having to modify any of their properties. ## GuiObjects GuiObjects are wrapper classes for a set of tkinter widgets with properties that are commonly used in this context. Their complexity varies; while labels are relatively simple, listboxes are composed of other GuiObjects surrounding the listbox and lots of additional behaviour. Properties of each GuiObject: - Each GuiObject is placed on a tkinter Frame object that is to be passed to the constructor - Position and dimensions are defined with x, y coordinates and width, height with w and h. Additionally, instead of x, y and w, h tuples, grid.xywh(GRID_CHAR_TAG) can be passed - The padding on the bottom/top and on the sides are defined as tuple padxy, which is a constructor arg - Sticky dictates to which side the widget will be pushed; N (north), E (east), S (south), W (west), C (centre), it can also be passed as NS, resulting in the widget being stuck against north and south edges, or other combinations (NWE is also possible) - Underlying Tk variables (TkStringVar for instance) are added as attributes by default. By passing one, that Var will be used instead. - event_bindings refer to specific event listeners native to tkinter and they can be passed with a corresponding method so it will be executed if that event is triggered. - Multiple event_bindings can be passed, e.g. LMB behavior and RMB behavior ### GuiListboxFrames and ListboxColumns Listboxes are defined as a frame that has various widgets on it. By default, a listbox, scrollbar, bottom label and top label are placed on the listbox frame. Furthermore, the listbox is filled with data from a pandas.DataFrame object. Passing a list of ListboxColumns can be used to automatically generate a button header on top of the listbox, while also allowing for adding sorting logic and specified row formats on a per-column basis. Various working examples can be found in gui_tab_inventory.InventoryFrame.top_listbox_colums. ### GuiGraph The GuiGraph is a Frame designed to display plotted graphs within the GUI. A call to GuiGraph.plot_graphs() with one or more graphs passed as argument will plot all the given graphs within this Frame. Graphs passed are typically Graph objects defined in the graphs module that have a Graph.generate_graph() method that returns the fully configured matplotlib.Axes object, which can be displayed in this frame. ### In practice ![gui_example](https://github.com/Maximuis94/OSRS-GE-Ledger/assets/7497371/cd7cf47e-2aa2-4429-86f8-70f2e36fdfec) This image shows the item prices interface, in which almost all GuiObjects as described above are present. # Data structures There are several data structures used within the project. External data is scraped indefinitely and imported at most once every 4 hours through a fully automated process, while transactions are generated by the user and are submitted by manually executing the transaction_parser/runelite_reader script, or by interacting with the transaction sqlite database in some other way. ## SQLite databases SQLite databases are used for logging transactions and for raw, external data provided by the data scraper. Data in the databases is by design meant to be read-only, unless there is a valid reason to modify the data. This data is used to generate NpyArrays for a subset of items and compute the Inventory, for instance. ## Inventory, Ledger and Transactions The Inventory, Ledger and Transaction are classes used to store trading results, transaction lists and individual trades, respectively. - A Transaction is a single trade, which is the purchase or sale of a specific quantity of a specific item for a specific price that occurred on a specific time. Additionally, it has a status flag that indicates whether it should be ignored (=0) or not (=1), a tag that describes how the Transaction was submitted and an update_timestamp that describes when the transaction was submitted. - The Ledger has a list of Transactions and various methods to interact with this list (fetch / modify / upload, among others) and its database. It is designed to streamline these interactions (e.g. by automatically assigning a unique transaction_id), but also by executing the appropriate SQLite statements with parameters derived from the transaction. - The Inventory is a collection of overall trading results which is produced by executing a series of transactions transactions logged in the ledger. Each item_id that has been traded has its own InventoryEntry object, which describes data resulting from executing these transactions. InventoryEntries are independently operating entities, as executing a transaction affects only one item directly. ## NpyArrays A NpyArray object is a preprocessed data structure that is created for items considered interesting and therefore worth tracking. This object is used as a central entity that can be used for accessing all available data for the item it represents, although the scope of this data is limited. By default, NpyArray data covers ~60 days of data (roughly 8 weeks) and is created right after importing external data. The NpyArray data for each item is saved locally in its own file. Rather than loading all available data, a list of attribute names can be passed to the NpyArray constructor instead as the attribute_subset arg. Including all extra attributes to the NpyArray class and implementing it into the updater protocol ensures that the data is updated every 4 hours for a subset of items after data is imported and makes it accessible through a single class. Attributes derived from combinations of existing values are added in the data_preprocessing.augment_scraped_data() method and are typically preceded by a commented line describing the attribute or how its computed.