blog:2021:0614_cryptoview_automatic_charts_update

CryptoView: automatic charts update

Good morning everyone! 5AM here and I haven't been sleeping since about 2:30AM… [Too much drinking last night I guess 🤢] So that was definitely a short sleep. But anyway, that's not the topic here: in this article I want to provide some improvements on my “CryptoView application” (short description below). Again, I will try to keep it short if I can, but no guarantee on that point I'm afraid 😂. For a start I simply want to automatically update the price charts I'm displaying in that app on a regular basis (every minute ? every 5 minutes ?) to always get the latest available price data. That's should not be too hard to implement I think, so let's get started.

But first, a quick overview on the application maybe [I mean, in case someone else than me is ever reading this… or if I come back in 5 years on this project and I dont remember a damn thing about it 😆]. I use this tool simply to display price charts for different cryptocurrencies, and at different timescales at the same time: I show a 24h chart, a 7 days chart, 30 days and max available duration for the currently selected currency.

The tool also allow the selection of the quote currency to use when drawing those charts (USD/BTC/ETH/BNB so far). And I also started adding some extension to this on a second tab to get some informations on the latest crypto token that were created on the different platforms (ethereum, binance smart chain, etc…). But, yeah, let's just keep it short for now: I don't think there is any need to go into additional on this for the purpose of this article.

Here are a couple of screenshots of what this tool looks like right now:

So, might not be worth much, but I really like those 4 charts display: I feel this can help to get a good overview on a given currency, so I'm using this almost on a daily basis in fact!

This design is inspired by what you can find on coingecko or coinmarketcap for instance: except that on those websites, you can only select one timesframe at a time.

One problem I have with that tool though is that currently, I'm retrieving all the available data for each currency at the very start of the app run, and then I don't update that data anymore… so if I leave the app running for a moment, then the display I get will not update to show any new price value of chart points as one could naturally expect: this is the behavior I would like to improve here.

Basically, I already have a function in my MonitoredPanel class (which is the first tab shown on the first image above) that will handle retrieving the all the available data from my price database:

    def updatePriceData(self):
        pdb = self.cg.getPriceDB()

        for cname in self.monitoredCoins:

            # Retrieve all the current available data on this coin:
            hisdata = pdb.getAllPriceData(PriceType.History, cname)
            logDEBUG("Retrieved %d rows of history data for %s" % (hisdata.shape[0], cname))


            hresdata = pdb.getAllPriceData(PriceType.HighRes, cname)
            logDEBUG("Retrieved %d rows of highres data for %s" % (hresdata.shape[0], cname))

            # The history/highres data should be a numpy array with the columns: timestamp,usd_price,usd_marketcap,usd_volume,btc_price,eth_price
            self.priceData[cname] = {
                'history': hisdata,
                'highres': hresdata
            }

Then I have another function that will retrieve the last row of the “highres” datasets I just updated with the function above, and use that to update the price display on the currency selection buttons:

    def updateCoinPrices(self):
        for cname in self.monitoredCoins:
            # Retrieve the highres data we have for that coin and get the last price value from there:
            hres = self.priceData[cname]['highres']
            lastRow = hres[-1]

            c = self.cg.getCoin(cname)
            usdPrice = lastRow[1]
            ethPrice = lastRow[5]
            btcPrice = lastRow[4]
            sym = c.symbol().upper()
            
            # To select what label we should display, we check what is the current quote:
            if self.currentQuote == 'usd':
                self.coinButtonList[cname].SetLabel("%s: %.4g$" % (sym, usdPrice))
            elif self.currentQuote == 'btc':
                self.coinButtonList[cname].SetLabel("%s: %.4g BTC" % (sym, btcPrice))
            else:
                self.coinButtonList[cname].SetLabel("%s: %.4g ETH" % (sym, ethPrice))

        self.Layout()

Similarly, I have another function loadPriceCharts(self, coinId) that I use to update the charts when I click a given button for instance.

⇒ So on the whole, all I really need here is to update the price datasets in an auxiliary thread, and then call on the main render thread the functions to update the button labels and the charts for the current currency, and that should be good enough 👌😎 So let's see “how terribly wrong” I could be here…

And here we go! I wasn't terribly wrong for a change, and it seems this is working fine already. I just created a new simple job class:

class UpdatePriceDataJob(FrameJob):    

    def __init__(self, frame):
        FrameJob.__init__(self, frame, "Updating price data...")

    def doJob(self):
        self.frame.monitoredPanel.updatePriceData()
        wx.CallAfter(self.frame.monitoredPanel.updateCoinPrices)
        wx.CallAfter(self.frame.monitoredPanel.loadPriceCharts)

Then on the main CryptoFrame class I setup a timer that is called every 60 seconds to trigger this job:

           self.timer = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.onTimerEvent, self.timer)

            connectStatusMessageHandler(self, self.onStatusMessageUpdated)
            self.worker.appendJob(UpdatePriceDataJob(self))

            self.timer.Start(60000)


    def onTimerEvent(self, event):
        self.worker.appendJob(UpdatePriceDataJob(self))

While I was at it, I also added support to display a default text on the chart as long as there is no data loaded. I used the indications from this documentation page to achieve this. And is was only a matter of updating a single line of code when constructing the chart panels:

class ChartPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.figure = Figure(figsize=(4, 3), dpi=75)
        self.axes = self.figure.add_subplot(111)
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.Fit()
        self.axes.text(0.5,0.5,"Please wait:\nUpdating price data...", size="large", ha='center', va='center', weight='demi')
        self.canvas.draw()

And this that change, at the very start of the app I now get this kind of display:

So I felt I could try and push it just a little bit further for this developmet session, and thus I thought I should add support to select whatever quote currency I want (among the available monitored coins) to draw the charts for the current currency: This should not be too hard in fact: I simply have to get the base currency prices in USD for instance and also get the same prices in USD for the quote currency, and simply divide those prices ? Let's check that.

I started with building a minimal interface choice element showing the list of all symbols:

        row = wx.BoxSizer(wx.HORIZONTAL)
        col.Add(row, 0, wx.TOP | wx.EXPAND, 5)
        # Add a label:
        lbl = wx.StaticText(self, label="Quote: ")
        row.Add(lbl, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0)

        choice = wx.Choice(self, choices = syms) 
        choice.SetSelection(0)
        row.Add(choice, 1, wx.LEFT, 5)
        choice.Bind(wx.EVT_CHOICE, self.onQuoteSelected)

Then in the event handler I find what coin is selected as quote (given the symbol):

        
    def onQuoteSelected(self, event):
        idx = event.GetSelection()
        # Find the coin corresponding to that symbol:
        cname = self.monitoredCoins[idx]

        # Should have the correct symbol:
        sym = self.cg.getCoinSymbol(cname).upper()
        CHECK(sym==event.GetString(), "Unexpected quote token symbol: %s != %s", sym, event.GetString())

        logDEBUG("Should use %s as quote currency" % cname)
        self.setQuoteCurrency(cname)

After that I had to update both functions updateCoinPrices(self) and loadPriceCharts(self, coinId) to support handling arbitrary quote currency, was a little bit tricky because we need to also retrieve the prices corresponding to the quote currency now (in USD), but at least this seems to be working fine for the moment. Here is what the newly added GUI elements look like:

And this is it for this time guys! Again, not a too long article in the end! [Not bad Manu… you're making progress! ✌😂]

  • blog/2021/0614_cryptoview_automatic_charts_update.txt
  • Last modified: 2021/06/14 10:47
  • by 127.0.0.1