├── README.md ├── trades.q ├── kdb.html ├── index.js └── kdb.py /README.md: -------------------------------------------------------------------------------- 1 | # Opencell 2 | 3 | Examples KDB+ backed streaming spreadsheet application, built with [Perspective streaming analytics library](https://github.com/finos/perspective). -------------------------------------------------------------------------------- /trades.q: -------------------------------------------------------------------------------- 1 | / trades.q 2 | / example table with random data 3 | 4 | trades:([] 5 | date:`date$(); 6 | time:`time$(); 7 | sym:`symbol$(); 8 | price:`real$(); 9 | size:`int$(); 10 | cond:`char$()) 11 | 12 | `trades insert (2013.07.01;10:03:54.347;`IBM;20.83e;40000;"N") 13 | `trades insert (2013.07.01;10:04:05.827;`MSFT;88.75e;2000;"B") 14 | trades 15 | 16 | syms:`IBM`MSFT`UPS`BAC`AAPL`JPMC 17 | tpd:100 / trades per day 18 | day:500 / number of days 19 | cnt:count syms / number of syms 20 | len:tpd*cnt*day / total number of trades 21 | date:2013.07.01+len?day 22 | time:"t"$raze (cnt*day)#enlist 09:30:00+15*til tpd 23 | time+:len?1000 24 | sym:len?syms 25 | price:len?100e 26 | size:100*len?1000 27 | cond:len?" ABCDENZ" 28 | 29 | trades 30 | -------------------------------------------------------------------------------- /kdb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * 3 | * Copyright (c) 2018, the Perspective Authors. 4 | * 5 | * This file is part of the Perspective library, distributed under the terms of 6 | * the Apache License 2.0. The full license can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | import perspective from "@finos/perspective"; 11 | import {PerspectiveDockPanel, PerspectiveWidget} from "@finos/perspective-phosphor"; 12 | import "@finos/perspective-phosphor/src/theme/material/index.less"; 13 | 14 | import "@finos/perspective-viewer-d3fc"; 15 | import "@finos/perspective-viewer-hypergrid"; 16 | 17 | import {Widget} from "@phosphor/widgets"; 18 | 19 | import "./style/index.less"; 20 | 21 | const PY_SERVER = "localhost:8888"; 22 | const CONN = perspective.websocket(`ws://${PY_SERVER}/perspective`); 23 | 24 | async function get_kdb(query) { 25 | const resp = await fetch(`http://${PY_SERVER}/kdb`, { 26 | method: "POST", 27 | body: query 28 | }); 29 | const name = await resp.text(); 30 | return CONN.open_table(name); 31 | } 32 | 33 | window.addEventListener("load", async () => { 34 | const workspace = new PerspectiveDockPanel("example"); 35 | Widget.attach(workspace, document.body); 36 | 37 | const perspective_viewer_1 = new PerspectiveWidget("One"); 38 | const perspective_viewer_2 = new PerspectiveWidget("Two"); 39 | workspace.addWidget(perspective_viewer_1); 40 | workspace.addWidget(perspective_viewer_2, {mode: "split-right", ref: perspective_viewer_1}); 41 | 42 | window.onresize = () => workspace.update(); 43 | 44 | const table = await get_kdb("trades"); 45 | perspective_viewer_1.load(table); 46 | perspective_viewer_2.load(table); 47 | }); 48 | -------------------------------------------------------------------------------- /kdb.py: -------------------------------------------------------------------------------- 1 | import tornado 2 | import tornado.web 3 | import tornado.websocket 4 | import tornado.escape 5 | import json 6 | import datetime 7 | import random 8 | import string 9 | 10 | import qpython.qconnection 11 | import perspective 12 | 13 | class ManagerMixin(object): 14 | def check_origin(self, origin): 15 | return True 16 | 17 | def set_default_headers(self): 18 | self.set_header("Access-Control-Allow-Origin", "*") 19 | self.set_header("Access-Control-Allow-Headers", "x-requested-with") 20 | self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') 21 | 22 | class KDBHandler(ManagerMixin, tornado.web.RequestHandler): 23 | # Query KDB+, get numpy.recarray 24 | # Create a perspective table 25 | # Host the table virtually so browsers can use it - returns an ID 26 | # Tell the client the virtual table's ID, which it will request via 27 | # websocket 28 | def initialize(self, manager, qconn): 29 | self._qconn = qconn 30 | self._manager = manager 31 | 32 | def post(self): 33 | query = self.request.body.decode("utf-8") 34 | narray = self._qconn(query) 35 | table = perspective.Table(narray) 36 | tbl_id = self._manager.host_table(table) 37 | self.write(tbl_id) 38 | 39 | class PerspectiveWebSocket(ManagerMixin, tornado.websocket.WebSocketHandler): 40 | # These are virtual calls from the browser - pass them through! 41 | def initialize(self, manager): 42 | self._session = manager.new_session() 43 | 44 | def on_message(self, message): 45 | self._session.process(message, self.write_message) 46 | 47 | def on_close(self): 48 | self._session.close() 49 | 50 | def start_server(manager, qconn): 51 | app = tornado.web.Application([ 52 | (r"/kdb", KDBHandler, {"qconn": qconn, "manager": manager}), 53 | (r"/perspective", PerspectiveWebSocket, {"manager": manager}) 54 | ]) 55 | app.listen(8888) 56 | print("Listening on http://localhost:8888") 57 | loop = tornado.ioloop.IOLoop.current() 58 | loop.start() 59 | 60 | if __name__ == "__main__": 61 | with qpython.qconnection.QConnection("localhost", 5000) as conn: 62 | manager = perspective.PerspectiveManager() 63 | start_server(manager = manager, qconn = conn) 64 | 65 | --------------------------------------------------------------------------------