Creating Custom Widgets Displayed in the Jupyter Notebook
Jupyter Notebook is an open-source web application that allows us to create and share documents that contain live code, equations, visualizations, and narrative text. It can be run on any web browsers and used as a tool for interactively developing and presenting data science projects. Because it is based on web technologies, we can use them with IPython to create custom widgets to be displayed in the Jupyter notebook. This article demonstrates how to develop custom widgets displayed in the Jupyter Notebook.
Widgets
Widgets are eventful python objects that have a representation in the browser such as sliders, Google charts, and Plotly charts. Widgets can be used to build interactive GUIs for your notebooks, synchronize information between Python and JavaScript, and display HTML elements in the Jupyter notebook. Jupyter notebook allows us to develop custom widgets to be presented in the Jupyter notebook.
In addition to Python and web technologies (HTML, CSS, and JavaScript) knowledge, there are three main things that we need to know before developing custom widgets.
1 Built-in magic commands
Magic commands are supported by the IPython kernel. They are prefixed by the % or %% characters. These magic commands are designed to solve common problems in data analysis and control the behavior of IPython itself. The magic command used to create custom widgets are:
- %%html is used to render and define HTML templates and cascading style sheets for widgets
- %%javascript is used to run the cell block of JavaScript code for widgets. Typically, it is used to create JavaScript modules and views for the widget front-end components
2. Traitlets
Traitlets is a framework that allows attributes in Python classes to support type checking, default values, and change events (observer pattern). Widgets use Traitlets to notify changes of the Python classes’ properties to JavaScript. Then JavaScript will update the HTML elements according to the changes.
3. Backbone.js
Backbone.js is a lightweight JavaScript library designed for developing single-page web applications. It is based on the MVC (model–view–controller) framework. The IPython widget framework relies heavily on Backbone.js for the front-end components.
In the next section, I will use this knowledge including CSS bootstrap and jQuery to create a Quote widget displaying real-time financial data in the Jupyter notebook.
Quote Widget
Quote Widget is a widget that displays real-time financial data in the Jupyter notebook.
There are three steps to implement this quote widget. All code is written in the Jupyter notebook (QuoteWidget.ipynb).
1. Python widget class
The first step is defining a Python class inheriting from the DOMWidget base class. The DOMWidget class defined in the ipywidgets package represents a widget that is displayed on the page as an HTML DOM element.
import ipywidgets as widgets
from traitlets import Unicode, Dict
class QuoteWidget(widgets.DOMWidget):
_view_name = Unicode('QuoteView').tag(sync=True)
_view_module = Unicode('Quote').tag(sync=True)
_view_module_version = Unicode('0.1.0').tag(sync=True)
payload = Dict().tag(sync=True)
status = Unicode("").tag(sync=True)
name = Unicode("Quote1").tag(sync=True)
The QuoteWidget class contains several traitlets properties. The _view_module, _view_module_version, and _view_name properties define the Backbone.js view class for the model. The payload, status, and name are the properties of the model. The name property contains the unique name of the quote widget. It represents a unique ID in the HTML DOM element. The status property contains a text representing the status of the quote widget. The payload property contains real-time data in the field list format.
{'BID': 21.2, 'BIDSIZE': 445800, 'QUOTIM': '08:20:06', 'SEQNUM': 3693049}
The data in the quote widget will be updated when the value of the payload property has been changed. The sync=True keyword argument tells the widget framework to synchronize the value to the Backbone.js front end.
2. HTML template
The next step is using the %%html built-in magic command to define an HTML template and styles for the quote widget. The template is defined inside the script tag with the text/template type. The ID of the template is quote-template. This template will be loaded by the Backbone.js front end. It uses the Bootstrap CSS framework to create a layout of the widget.
%%html
<style>
.update {
color: yellow;
}
.arrow-up {
vertical-align: top;
}
.arrow-down {
vertical-align: middle;
}
.quote_label {
color: DeepSkyBlue;
}
…
</style>
<script type="text/template" id="quote-template">
<div class="container quote_box">
<div class="row quote_title">
<div>
<span data-field="X_RIC_NAME"></span> Quote
</div>
</div>
<div class="row quote_header">
<div class="col-xs-2" data-field="X_RIC_NAME"> </div>
<div class="col-xs-3" data-field="DSPLY_NAME"> </div>
<div class="col-xs-1" data-field="RDN_EXCHD2"> </div>
<div class="col-xs-1" data-field="CURRENCY"> </div>
<div class="col-xs-2 text-center">GMT</div>
<div class="col-xs-2" data-field="TRADE_DATE"> </div>
</div>
…
The data-field attribute is used to define where the value of the field will be displayed and the data-animated attribute indicates the style of the element will be changed when the value has been updated.
<div class="col-xs-1 text-center" data-field="BIDSIZE" data-animated></div>
<div class="col-xs-1 text-center" data-field="BID" data-animated></div>
<div class="col-xs-1 text-center" data-field="ASK" data-animated></div>
<div class="col-xs-1 text-center" data-field="ASKSIZE" data-animated></div>
<div class="col-xs-1 text-center" data-field="QUOTIM" data-animated></div>
This template supports the following fields.
Field Name | Field Description |
X_RIC_NAME | The symbol representing the instrument (eg. RIC) |
DSPLY_NAME | Full or abbreviated text instrument name |
RDN_EXCHD2 |
The identifier for the source from where the data originates |
CURRENCY | The currency in which the instrument is quoted |
TRADE_DATE | The date of the value in the field TRDPRC_1 |
TRDPRC_1 | Last trade price or value |
TRDPRC_2 | Previous last trade prices or values |
TRDPRC_3 | Previous last trade prices or values |
TRDPRC_4 | Previous last trade prices or values |
TRDPRC_5 | Previous last trade prices or values |
PRCTCK_1 |
The direction of price movement from the previous trade or "Last" price |
TRDVOL_1 |
Number of shares, lots or contracts traded in the most recent transaction |
TRDTIM_1 |
Time of the value in the TRDPRC_1 in minutes. |
NETCHNG_1 |
Difference between the lastest trading price or value and the adjusted historical closing value or settlement price. |
PCTCHNG |
Percentage change of the latest trade price or value from the adjusted historical close. |
BIDSIZE |
The number of shares, lots, or contracts willing to buy at the Bid price |
BID |
Latest Bid Price (price willing to buy) |
ASK |
Latest Ask Price (price offering to sell) |
ASKSIZE |
The number of shares, lots, or contracts willing to sell at the Ask price |
QUOTIM |
Quote time given in seconds |
OPEN_PRC |
Today's opening price or value |
ACVOL_1 |
The accumulated number of shares, lots or contracts traded according to the market convention |
VWAP |
Volume Weighted Average Price |
HIGH_1 |
Today's highest transaction value |
52WK_HIGH |
The high from the previous 52 weeks |
TURNOVER |
The daily turnover revenue or value of all shares for either a particular instrument or an exchange |
LOW_1 |
Today's lowest transaction value |
52WK_LOW |
The low from the previous 52 weeks |
EARNINGS |
Latest reported earnings per share |
HST_CLOSE |
Historical unadjusted close or settlement price |
ADJUST_CLS |
Closing Price adjusted for corporate actions |
PERATIO |
Ratio of stock price to earnings per share |
3. Backbone.js front end
The last step is using the %%javascript built-in magic command to define a Backbone.js front end. .This is the main component used to control and display the widget. In the IPython widget framework, Backbone.js is used to create a front end’s module and view linked to the Python class defined in the first step. The values of _view_name (QuoteView) and _view_module (Quote) properties in the first step are used to define the front end module and view.
First, it uses require.js to define the Quote module which depends on the @jupyter-widgets/base module. Then, it uses the extend method to create the QuoteView class by inheriting from the DOMWidgetView class.
%%javascript
require.undef('Quote');
define('Quote', ["@jupyter-widgets/base"], function(widgets) {
var QuoteView = widgets.DOMWidgetView.extend({
});
return {
QuoteView: QuoteView
}
});
Next, it uses underscore.js to load the HTML template created in the second step, and then override the base render method of the view to add the template into the HTML DOM document. It uses the name property defined in the first step as a unique ID of the DOM element.
var QuoteView = widgets.DOMWidgetView.extend({
template: _.template($("#quote-template").html()),
render: function(){
var name = this.model.get('name');
this.model.on('change:payload',this.payload_changed, this);
this.model.on('change:status',this.status_changed, this);
this.div = $('<div/>',{id: name}).append(this.template);
$(this.el).append(this.div);
},
In the render function, the model.on method is called to register functions (payload_changed, and status_changed) to update the view’s values when the payload and status properties change.
The status_changed function is called when a value of the status property changes.
status_changed: function(){
var status_text = this.model.get('status');
var name = this.model.get('name');
var statusField = $("#"+name+" [data-field=status_text]");
statusField.text(status_text);
},
This function uses jQuery to select an HTML element in the widget that has the “status_text” in the data-field attribute, and then update the text attribute of the selected HTML element to the value of the status property.
<div class="col-xs-11" data-field="status_text"></div>
The payload_changed function is called when a value of the payload property changes.
payload_changed: function(){
var payload = this.model.get('payload');
var name = this.model.get('name');
Object.getOwnPropertyNames(payload).forEach(
(value, index, array)=>
{
var updatedField = $("#"+name+" [data-field="+value+"]");
check_ripple(name, updatedField);
if(updatedField.data('value')!=undefined){
var selvalue = updatedField.data('value');
updatedField.removeClass().addClass(selvalue[payload[value]]);
}else{
updatedField.text(payload[value]);
}
if(updatedField.data('animated')!=undefined){
updatedField.addClass('update');
setTimeout(function() {
updatedField.removeClass('update');
}, 1000);
}
});
}
This function iterates all fields in the payload. For each field, it does the following tasks:
· Use jQuery to select an HTML element in the widget that has the updated field’s name in the data-field attribute and then update the text attribute of the selected HTML element to the field’s value
<div class="col-xs-3" data-field="DSPLY_NAME"> </div>
· Handle the ripple fields via the data-ripple attribute where the previous value of the updated field is moved to another field when the field has been updated
<span data-field="TRDPRC_1" class="quote_trade_price1" data-ripple="TRDPRC_2" data-animated></span>
· Change the style of the updated HTML element for 1 second if it contains the data-animated attribute. It is used to notify users that the field’s value has been updated
<div class="col-xs-1 text-center" data-field="BID" data-animated></div>
Quote Widget Usage
The Quote Widget is implemented in the QuoteWidget.ipynb file. To use the widget, please follow these steps:
1. Use the %run built-in magic command to run the QuoteWidget.ipynb file
%run ./QuoteWidget.ipynb
2. Create a QuoteWidget and set a unique name
quote = QuoteWidget()
quote.name = "Quote1"
quote
3. Set the payload and status properties to update the widget
quote.payload = {'DSPLY_NAME': 'BANGKOK DUSIT ME', 'RDN_EXCHD2': 'SET', 'CURRENCY': 'THB', 'NETCHNG_1': -0.1, 'PCTCHNG': -0.47, 'TRDVOL_1': 1000, 'TRADE_DATE': '2021-01-18', 'TRDTIM_1': '08:19:41', 'TRDPRC_1': 21.3, 'TRDPRC_2': 21.3, 'TRDPRC_3': 21.2, 'TRDPRC_4': 21.3, 'TRDPRC_5': 21.3, 'PRCTCK_1': 1, 'BID': 21.2, 'BIDSIZE': 585900, 'ASK': 21.3, 'ASKSIZE': 936900, 'OPEN_PRC': 21.4, 'ACVOL_1': 17884500, 'VWAP': 21.2084, 'VWAP_VOL': 17884500, 'HIGH_1': 21.5, '52WK_HIGH': 26.5, 'TURNOVER': 379301.91, 'LOW_1': 21.1, '52WK_LOW': 15.6, 'PERATIO': 47.58, 'HST_CLOSE': 21.4, 'ADJUST_CLS': 21.4, 'EARNINGS': 0.4498, 'QUOTIM': '08:19:55', 'SEQNUM': 3690282, 'X_RIC_NAME': 'BDMS.BK'}
quote.status = "Open/Ok"
Moreover, the Quote Widget can be used with any APIs that support Real-Time financial data, such as Refinitiv Eikon Data API, Refinitiv WebSocket API, and Refinitv Data Platform. The sample code (EDAPIQuoteWidget.ipynb) for Eikon Data API is available on GitHub. To use EDAPIQuoteWidget, please follow these steps:
1. Use the %run built-in magic command to run the EDAPIQuoteWidget.ipynb file
%run ./EDAPIQuoteWidget.ipynb
2. Import the Eikon Data API package and set the application key
import eikon as ek
ek.set_app_key('<application key>')
3. Load data dictionary files used to expand enumerated strings for the enumeration fields
dict = RDMFieldDictionary("RDMFieldDictionary", "enumtype.def")
4. Create an EDAPIQuoteWidget with the Eikon Data API, widget name, and data dictionary, and then call the widget method to display the widget
q = EDAPIQuoteWidget(ek, "quote1", dict)
q.widget()
5. Call the open method with the instrument name to subscribe to the Real-Time service. The retrieved real-time data will be displayed on the widget
q.open("AV.L")
Summary
This article demonstrates how to develop custom widgets displayed on the Jupyter Notebook. The widget framework uses Traitlets to notify changes of the Python classes’ attributes to JavaScript and uses Backbone.js for the front end javascript parts. Then, it shows steps to create a quote widget for displaying financial real-time on the Jupyter Notebook. Finally, it presents how to use the quote widget with Refinitiv Eikon Data API. All widget and example files are available on GitHub.
References
- Backbonejs.org. n.d. Backbone.Js. [online] Available at: <https://backbonejs.org/> [Accessed 20 January 2021].
- Ipywidgets.readthedocs.io. 2017. Building A Custom Widget - Email Widget — Jupyter Widgets 7.6.2 Documentation. [online] Available at: <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Custom.html> [Accessed 20 January 2021].
- Ipython.readthedocs.io. n.d. Built-In Magic Commands — Ipython 7.19.0 Documentation. [online] Available at: <https://ipython.readthedocs.io/en/stable/interactive/magics.html> [Accessed 20 January 2021].
- Developers.refinitiv.com. n.d. Eikon Data API | Refinitiv Developers. [online] Available at: <https://developers.refinitiv.com/en/api-catalog/eikon/eikon-data-api> [Accessed 20 January 2021].
- Jquery.com. n.d. Jquery. [online] Available at: <https://jquery.com/> [Accessed 20 January 2021].
- Mark Otto, a., n.d. Bootstrap. [online] Getbootstrap.com. Available at: <https://getbootstrap.com/> [Accessed 20 January 2021].
- Developers.refinitiv.com. n.d. Refinitiv Data Platform Libraries | Refinitiv Developers. [online] Available at: <https://developers.refinitiv.com/en/api-catalog/refinitiv-data-platform/refinitiv-data-platform-libraries> [Accessed 20 January 2021].
- Developers.refinitiv.com. n.d. Refinitiv Websocket API | Refinitiv Developers. [online] Available at: <https://developers.refinitiv.com/en/api-catalog/elektron/refinitiv-websocket-api> [Accessed 20 January 2021].
- Rossant, C., 2018. Ipython Interactive Computing And Visualization Cookbook, Second Edition. 2nd ed. Birmingham: Packt Publishing.
- Traitlets.readthedocs.io. 2015. Traitlets — Traitlets 5.0.5 Documentation. [online] Available at: <https://traitlets.readthedocs.io/en/stable/> [Accessed 20 January 2021].