changeset 5:952e169e0b7e

Make buy/sell buttons functional and display depot value
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 01 Mar 2019 14:20:28 +0100
parents 7a722bb179ff
children 2063f73a8e6f
files client/client.py server/server.py
diffstat 2 files changed, 145 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/client/client.py	Fri Mar 01 13:19:32 2019 +0100
+++ b/client/client.py	Fri Mar 01 14:20:28 2019 +0100
@@ -106,6 +106,72 @@
         creds.addr = self.addr.text()
         return creds
 
+# A Depot instance is shared by DepotStocks.
+class Depot(core.QObject):
+    cash = 0
+    stock = {}
+
+    priceUpdated = core.pyqtSignal(str)
+
+    def add_stock(self, stocksym, stock):
+        if stocksym not in self.stock:
+            self.stock[stocksym] = stock
+
+    def buy(self, stocksym, num):
+        if stocksym not in self.stock:
+            raise AttributeError('stock not found!')
+        stock = self.stock[stocksym]
+        price = stock.current_price
+        assert price > 0
+        if price * num > self.cash:
+            return False
+        self.cash -= price*num
+        stock.current_num += num
+        self.priceUpdated.emit(stocksym)
+        return True
+
+    def sell(self, stocksym, num):
+        if stocksym not in self.stock:
+            raise AttributeError('stock not found!')
+        stock = self.stock[stocksym]
+        price = stock.current_price
+        assert price > 0
+        if num > stock.current_num:
+            return False
+        self.cash += price*num
+        stock.current_num -= num
+        return True
+
+    def update(self, message):
+        for sym, upd in message.items():
+            if sym.startswith('_'):
+                continue
+            if sym in self.stock:
+                self.stock[sym].update(upd)
+                self.priceUpdated.emit(sym)
+
+# A stock position in a depot.
+class DepotStock:
+    sym = ''
+    mydepot = None
+
+    current_price = -1
+    current_num = 0
+    MAXHIST = 500
+    price_history = []
+
+    def __init__(self, sym):
+        self.sym = sym
+
+    def update(self, upd):
+        if self.current_price >= 0:
+            self.price_history.append(self.current_price)
+            if len(self.price_history) > self.MAXHIST:
+                self.price_history = self.price_history[25:]
+        self.current_price = upd['price']
+        if upd['split']:
+            self.current_num = self.current_num * 2
+
 class StockGraph(chart.QChartView):
     sym = ''
 
@@ -119,7 +185,7 @@
 
     def __init__(self, sym, dim):
         super().__init__()
-        super().setMinimumSize(300,300)
+        super().setMinimumSize(300,200)
         self.sym = sym
         self.series = chart.QLineSeries(self)
         for x in self.XAXIS:
@@ -139,6 +205,7 @@
         previous, nxt = (self.current-1)%self.MAX_LEN, (self.current+1)%self.MAX_LEN
         # Shift graph to the left
         self.series.replace(self.current, self.current, value)
+        self.series.replace(nxt, nxt, 0)
 
         self.current += 1
         if self.current >= self.MAX_LEN:
@@ -152,10 +219,16 @@
 
 class StockWidget(wid.QWidget):
     graph = None
+    depot = None
+    sym = ''
+    depotstock = None
 
-    def __init__(self, graph):
+    def __init__(self, graph, depot, depotstock):
         super().__init__()
         self.graph = graph
+        self.depot = depot
+        self.depotstock = depotstock
+        self.sym = self.depotstock.sym
 
         mainvbox = wid.QVBoxLayout(self)
         mainvbox.addWidget(self.graph)
@@ -163,7 +236,9 @@
 
     def init_buttonbox(self):
         buy = wid.QPushButton('  BUY   ')
+        buy.clicked.connect(self.on_buy)
         sell = wid.QPushButton('  SELL  ')
+        sell.clicked.connect(self.on_sell)
         self.current_state = wid.QLineEdit()
         self.current_state.setReadOnly(True)
         self.current_state.setAlignment(core.Qt.AlignCenter)
@@ -174,9 +249,49 @@
         hbox.addWidget(self.current_state)
         return hbox
 
-    def update(self, val):
-        self.graph.update_stock(val/100)
-        self.current_state.setText('{} pc / {} ø/pc / {} ø'.format('?', val/100, val/100))
+    def on_buy(self):
+        if not self.depot.buy(self.sym, 1):
+            print("Warning: couldn't buy {}".format(self.depotstock.sym))
+        self.update(self.sym)
+
+    def on_sell(self):
+        if not self.depot.sell(self.sym, 1):
+            print("Warning: couldn't sell {}".format(self.depotstock.sym))
+        self.update(self.sym)
+
+    # Triggered by the depot when there is new data for any stock (so we filter if the update is for us)
+    @core.pyqtSlot(str)
+    def update(self, sym):
+        if sym != self.sym:
+            return
+        val = self.depotstock.current_price / 100
+        self.graph.update_stock(val)
+        self.current_state.setText('{} pc / {} ø/pc / {} ø'.format(self.depotstock.current_num,
+                    self.depotstock.current_price, self.depotstock.current_num
+                    * self.depotstock.current_price))
+
+class DepotWidget(wid.QWidget):
+    depot = None
+    hbox = None
+    depot_value_widget = None
+
+    def __init__(self, depot):
+        super().__init__()
+        self.depot = depot
+        self.depot.priceUpdated.connect(self.on_depot_update)
+
+        self.hbox = wid.QHBoxLayout(self)
+        self.depot_value_widget = wid.QLineEdit()
+        self.depot_value_widget.setReadOnly(True)
+        self.depot_value_widget.setAlignment(core.Qt.AlignCenter)
+        self.hbox.addWidget(wid.QLabel('Current Depot Value: '))
+        self.hbox.addWidget(self.depot_value_widget)
+
+        self.on_depot_update('')
+
+    @core.pyqtSlot(str)
+    def on_depot_update(self, sym_):
+        self.depot_value_widget.setText('{} ø'.format(self.depot.cash))
 
 class ClientSocket(core.QObject):
     zctx = None
@@ -220,11 +335,16 @@
     """
 
     creds = Creds()
+    depot = Depot()
+    depot_widget = None
 
     def __init__(self):
         super(wid.QWidget, self).__init__()
         super(arguments.BaseArguments, self).__init__(doc=self._doc)
 
+        self.depot_widget = DepotWidget(self.depot)
+        self.depot.cash = 1000000
+
         ccd = ClientConfigDialog(self, defaults=self.defaults)
         ccd.accepted.connect(lambda: self.set_creds(ccd.creds()))
         ccd.accepted.connect(self.start_wait_window)
@@ -239,6 +359,7 @@
 
         self.start_main_window()
         self.waiting = wid.QLabel("Waiting for incoming stock data - hang tight!", self)
+        self.mainvbox.addWidget(self.depot_widget)
         self.mainvbox.addWidget(self.waiting)
 
     stock_widgets = {}
@@ -246,21 +367,22 @@
     @core.pyqtSlot(dict)
     def on_new_data(self, stockdata):
         self.waiting.hide()
-        for sym, val in sorted(stockdata.items()):
-            if sym in self.stock_widgets:
-                self.stock_widgets[sym].update(val)
-            elif sym != '_stockdata':
+        self.depot.update(stockdata)
+        for sym, upd in sorted(stockdata.items()):
+            if sym != '_stockdata' and sym not in self.stock_widgets:
+                depotstock = DepotStock(sym)
                 sg = StockGraph(sym, None)
-                sw = StockWidget(sg)
+                sw = StockWidget(sg, self.depot, depotstock)
                 self.stock_widgets[sym] = sw
                 self.add_stock_widget(sw)
+                self.depot.add_stock(sym, depotstock)
+                self.depot.priceUpdated.connect(sw.update)
 
     mainvbox = None
     hboxes = []
     widgets_per_hbox = 2
 
     def add_stock_widget(self, sw):
-        print(self.hboxes)
         if len(self.hboxes) == 0 or self.hboxes[-1].count() >= self.widgets_per_hbox:
             hbox = wid.QHBoxLayout()
             hbox.addWidget(sw)
@@ -272,7 +394,6 @@
 
     def start_main_window(self):
         self.mainvbox = wid.QVBoxLayout(self)
-        self.main_window_active = True
         self.show()
 
 def main():
--- a/server/server.py	Fri Mar 01 13:19:32 2019 +0100
+++ b/server/server.py	Fri Mar 01 14:20:28 2019 +0100
@@ -16,6 +16,7 @@
 _random.seed(1)
 # Maximum initial stock value in cents.
 _maxinitvalue = 10000
+_splitvalue = 20000
 _maxhistory = 100
 
 class Stock:
@@ -37,15 +38,22 @@
         self._current_value = _random.random() * _maxinitvalue
 
     def next_price(self):
-        """Calculates a (random) next price based on the current price and history."""
+        """Calculates a (random) next price based on the current price and history. Returns a dict suitable for inclusion in a _stockdata object."""
         dev = 0.02*self._current_value or 1
         new_value = int(_random.normalvariate(self._current_value * 1.001, dev))
         new_value = abs(new_value)
+        split = False
+
+        if new_value > _splitvalue:
+            new_value = new_value / 2
+            split = True
+
         self._last_values.append(self._current_value)
         self._current_value = new_value
         if len(self._last_values) > _maxhistory:
             self._last_values = self._last_values[1:]
-        return new_value
+
+        return {'price': new_value, 'split': split, '_stockupdate': True}
 
     def current_value(self):
         return self._current_value
@@ -83,7 +91,8 @@
     def generate(self):
         next = {}
         for s in self._stocks:
-            next[s.symbol] = s.next_price()
+            nextprice = s.next_price()
+            next[s.symbol] = nextprice
         return StockData(next)