├── 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 |
--------------------------------------------------------------------------------