Working and relatively glitch free. Classes are being implemented in javascript files. Python is basically one big class with no separation.

This commit is contained in:
Rob 2022-05-10 08:12:45 -03:00
parent 42da478944
commit b6d9bccaaf
4 changed files with 546 additions and 0 deletions

187
static/charts.js Normal file
View File

@ -0,0 +1,187 @@
class Charts {
constructor(idata) {
// Unpack the initialization data.
this.chart1_id = idata.chart1_id;
this.chart2_id = idata.chart2_id;
this.chart3_id = idata.chart3_id;
this.trading_pair = idata.trading_pair;
this.price_history = idata.price_history;
/* A list of bound charts this is necessary for maintaining a dynamic
number of charts with their position and zoom factors bound. I had to
declare it global as it is accessed through a callback.*/
window.bound_charts=[];
// Only the main chart is created by default.
this.create_main_chart();
}
create_main_chart() {
// Pass the id of the element to create the main chart in.
// This function returns the main chart object.
this.chart_1 = this.create_chart(this.chart1_id);
// Display the trading pair as a watermark overlaying the chart.
this.addWatermark(this.chart_1, this.trading_pair);
// - Create the candle stick series for our chart
this.candleSeries = this.chart_1.addCandlestickSeries();
//Initialise the candlestick series
this.price_history.then((ph) => {
//Initialise the candle data
this.candleSeries.setData(ph);
})
this.bind_charts(this.chart_1);
}
create_RSI_chart(){
this.chart2 = this.create_chart(this.chart2_id, 100);
this.set_priceScale(this.chart2, 0.3, 0.0);
// Put the name of the chart in a watermark in the chart.
this.addWatermark(this.chart2, 'RSI');
// Todo: Not sure how to set this
//this.chart2.applyOptions({ priceRange: {minValue:0,maxValue:100} });
this.bind_charts(this.chart2);
}
create_MACD_chart(){
this.chart3 = this.create_chart(this.chart3_id, 100);
this.addWatermark(this.chart3, 'MACD');
this.bind_charts(this.chart3);
}
create_chart(target_id, height=500){
// Accepts a target element to place the chart in.
// Returns the chart object.
let container = document.getElementById(target_id);
//Create a lightweight chart object.
let chart = LightweightCharts.createChart(container, {
width: 1000,
height: height,
crosshair: {
mode: LightweightCharts.CrosshairMode.Normal,
},
priceScale: {
borderColor: 'rgba(197, 203, 206, 0.8)',
},
timeScale: {
borderColor: 'rgba(197, 203, 206, 0.8)',
timeVisible: true,
secondsVisible: false,
barSpacing: 6
},
handleScroll: true
});
return chart;
}
set_priceScale(chart, top, bottom){
chart.priceScale('right').applyOptions({
scaleMargins: {
top: top,
bottom: bottom,
},
});
}
addWatermark(chart,text){
chart.applyOptions({
watermark: {visible: true,
color: '#DBC29E',
text: text,
fontSize: 30,
fontFamily: 'Roboto',
fontStyle: 'bold',
vertAlign: 'center'
}
});
}
bind_charts(chart){
// keep a list of charts and bind all their position and spacing.
// Add (arg1) to bound_charts
this.add_to_list(chart);
// Get the number of objects in bound_charts
let bcl = Object.keys(window.bound_charts).length;
// if bound_charts has two element in it bind them
if (bcl == 2) { this.bind2charts(); }
// if bound_charts has two element in it bind them
if (bcl == 3) { this.bind3charts(); }
return;
}
add_to_list(chart){
// If the chart isn't already included in the list, add it.
if ( !window.bound_charts.includes(chart) ){
window.bound_charts.push(chart);
}
}
bind2charts(){
//On change in chart 1 change chart 2
window.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler1);
function syncHandler1(e) {
// Get the barSpacing(zoom) and position of 1st chart.
let barSpacing1 = window.bound_charts[0].timeScale().getBarSpacing();
let scrollPosition1 = window.bound_charts[0].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to 2nd chart.
window.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
}
//On change in chart 2 change chart 1
window.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
function syncHandler2(e) {
// Get the barSpacing(zoom) and position of chart 2
let barSpacing2 = window.bound_charts[1].timeScale().getBarSpacing();
let scrollPosition2 = window.bound_charts[1].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to chart 1
window.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
}
bind3charts(){
//On change to chart 1 change chart 2 and 3
window.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler);
function syncHandler(e) {
// Get the barSpacing(zoom) and position of chart 1
let barSpacing1 = window.bound_charts[0].timeScale().getBarSpacing();
let scrollPosition1 = window.bound_charts[0].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to new chart
window.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
window.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
}
//On change to chart 2 change chart 1 and 3
window.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
function syncHandler2(e) {
// Get the barSpacing(zoom) and position of chart 2
let barSpacing2 = window.bound_charts[1].timeScale().getBarSpacing();
let scrollPosition2 = window.bound_charts[1].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to chart 1 and 3
window.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
window.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
//On change to chart 3 change chart 1 and 2
window.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncHandler3);
function syncHandler3(e) {
// Get the barSpacing(zoom) and position of new chart
let barSpacing2 = window.bound_charts[2].timeScale().getBarSpacing();
let scrollPosition2 = window.bound_charts[2].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to parent chart
window.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
window.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
}
}

79
static/communication.js Normal file
View File

@ -0,0 +1,79 @@
class Communication {
constructor(interval, ocu, occ, oiu) {
// Register callbacks
this.on_candle_update = ocu;
this.on_candle_close = occ;
this.on_indctr_update = oiu;
// Open connections.
this.set_app_con();
this.set_exchange_con(interval);
}
candle_update(new_candle){
this.on_candle_update(new_candle);
}
candle_close(new_candle){
// Send a copy of the data to the server
this.app_con.send( JSON.stringify({ message_type: "candle_data", data :new_candle }));
this.on_candle_close(new_candle);
}
indicator_update(data){
this.this.on_indctr_update(data);
}
set_app_con(){
// Create a web socket connection to our app.
this.app_con = new WebSocket('ws://localhost:5000/ws');
this.app_con.onopen = () => this.app_con.send("Connection OK");
this.app_con.addEventListener('message', ev => {
if(ev.data){
// Get the message received from server
let msg = JSON.parse(ev.data)
// Handle a request from the server
if (msg.request) {
//handle request
console.log('Received a request from the server');
console.log(msg.request);
}
// Handle a reply from the server
if (msg.reply) {
// Handle indicator updates
if (msg.reply == 'i_updates'){
// console.log(msg.data);
this.indicator_update(msg.data)
}
}
}
})
}
set_exchange_con(interval){
let ws = "wss://stream.binance.com:9443/ws/btcusdt@kline_" + interval;
this.exchange_con = new WebSocket(ws);
// Set the on-message call-back for the socket
this.exchange_con.onmessage = (event) => {
// Convert message to json obj
let message = JSON.parse(event.data);
// Isolate the candle data from message
let candlestick = message.k;
//console.log(message.k)
// Reformat data for lightweight charts
let new_candle={
time: candlestick.t / 1000,
open: candlestick.o,
high: candlestick.h,
low: candlestick.l,
close: candlestick.c,
vol: candlestick.v
};
// Update frequently updated objects
this.candle_update(new_candle);
// Update less frequently updated objects.
if (candlestick.x == true) { this.candle.close(new_candle); }
}
}
}

22
static/data.js Normal file
View File

@ -0,0 +1,22 @@
class Data {
constructor() {
// Set the id's of the HTML elements that contain each section of the user interface.
this.chart1_id = 'chart';
this.chart2_id = 'chart2';
this.chart3_id = 'chart3';
// Get and set into memory configuration and historical data from the server.
this.trading_pair = bt_data.trading_pair;
this.interval = bt_data.interval;
this.price_history = fetch('http://localhost:5000/history').then((r) => r.json()).then( (data) => { return data; } );
this.indicators = bt_data.indicators;
// Request initialization data for the indicators.
this.indicator_data = fetch('http://localhost:5000/indicator_init').then((r) => r.json()).then( (data) => { return data; } );
}
candle_update(){
console.log('candle update');
}
candle_close(){
console.log('candle close');
}
}

258
static/indicators.js Normal file
View File

@ -0,0 +1,258 @@
class Indicator {
constructor(name) {
// The name of the indicator.
this.name = name;
this.lines=[];
this.hist=[];
}
init(data){
console.log(this.name + ': init() unimplemented.');
}
update(data){
console.log(this.name + ': update() unimplemented.');
}
addHist(name, chart, color='#26a69a'){
this.hist[name] = chart.addHistogramSeries({
color: color,
priceFormat: {
type: 'price',
},
priceScaleId: '',
scaleMargins: {
top: 0.0,
bottom: 0,
},
});
}
addLine(name, chart, color, lineWidth){
this.lines[name] = chart.addLineSeries({
color: color,
lineWidth: lineWidth
});
}
setLine(name, data, value_name){
// Initialize the data with the data object provided.
this.lines[name].setData(data);
// Isolate the last value provided and round to 2 decimals places.
let priceValue = data.at(-1).value;
this.updateDisplay(name, priceValue, value_name);
}
updateDisplay(name, priceValue, value_name){
let rounded_value = (Math.round(priceValue * 100) / 100).toFixed(2);
// Update the data in the edit and view indicators panel
document.getElementById(this.name + '_' + value_name).value = rounded_value;
//Initialise the legend todo fix this
//set_legend_text(rounded_value, i);
}
setHist(name, data){
this.hist[name].setData(data);
}
updateLine(name, data, value_name){
// Update the line-set data in the chart
this.lines[name].update(data);
// Update the data in the edit and view indicators panel
this.updateDisplay(name, data, value_name);
}
updateHist(name,data){
this.hist[name].update(data);
}
}
class SMA extends Indicator{
constructor(name, chart, color, lineWidth = 2) {
// Call the inherited constructor.
super(name);
// Create a line series and append to the appropriate chart.
this.addLine('line', chart, color, lineWidth);
// todo: pass in indicator output or something? Regiyhhster crosshair movements to the indicators output panel
//create_legend(name, chart);
}
init(data){
this.setLine('line',data, 'value');
}
update(data){
this.updateLine('line', data, 'value');
}
}
class Linear_Regression extends SMA{
}
class EMA extends SMA{
}
class RSI extends Indicator{
constructor(name, charts, color, lineWidth = 2) {
// Call the inherited constructor.
super(name);
// If the chart doesn't exsist create one.
if( !charts.hasOwnProperty('chart2') ) { charts.create_RSI_chart(); }
let chart = charts.chart2;
// Create a line series and append to the appropriate chart.
this.addLine('line', chart, color, lineWidth);
// todo: pass in indicator output or somthing? Register crosshair movements to the indicators output panel
//create_legend(name, chart);
}
init(data){
this.setLine('line',data, 'value');
}
update(data){
this.updateLine('line', data, 'value');
}
}
class MACD extends Indicator{
constructor(name, charts, color_m, color_s, lineWidth = 2) {
// Call the inherited constructor.
super(name);
// If the chart doesn't exsist create one.
if( !charts.hasOwnProperty('chart3') ) { charts.create_MACD_chart(); }
let chart = charts.chart3;
// Create two line series and append to the chart.
this.addLine('line_m', chart, color_m, lineWidth);
this.addLine('line_s', chart, color_s, lineWidth);
this.addHist(name, chart);
// todo: pass in indicator output or somthing? Register crosshair movements to the indicators output panel
//create_legend(name, chart);
}
init(data){
this.setLine('line_m',data[0], 'macd');
this.setLine('line_s',data[1], 'signal');
this.setHist(name, data[2]);
}
update(data){
this.updateLine('line_m', data[0], 'macd');
this.updateLine('line_s', data[1], 'signal');
this.addHist(name, data[3]);
}
}
class ATR extends Indicator{
init(data) {
this.updateDisplay(this.name, data.at(-1).value, 'value');
}
update(data) {
this.updateDisplay(this.name, data.value, 'value');
}
}
class Volume extends Indicator{
constructor(name, chart) {
// Call the inherited constructor.
super(name);
this.addHist(name, chart);
this.hist[name].applyOptions( { scaleMargins: { top: 0.8, bottom: 0.0} } );
}
init(data){
this.setHist(this.name, data);
}
update(data){
this.updateHist(this.name, data);
}
}
class Bolenger extends Indicator{
constructor(name, chart, color_u, color_m, color_l, lineWidth = 2) {
// Call the inherited constructor.
super(name);
// Create three line series and append to the chart.
this.addLine('line_u', chart, color_u, lineWidth);
this.addLine('line_m', chart, color_u, lineWidth);
this.addLine('line_l', chart, color_u, lineWidth);
}
init(data){
// Initialize the data with the data object provided.
this.setLine('line_u',data[0],'value1');
this.setLine('line_m',data[1],'value2');
this.setLine('line_l',data[2],'value3');
}
update(data){
// Update the line-set data in the chart
this.line_u.update(data[0], 'value1');
// Update the line-set data in the chart
this.line_m.update(data[1]), 'value2';
// Update the line-set data in the chart
this.line_l.update(data[2], 'value3');
}
}
class Indicators {
constructor(charts, idata) {
// Create an array to hold all the created indicators.
this.indicators = [];
// Pass a list of indicators from received from the server
// to a function that will create them.
this.create_indicators(idata.indicators, charts);
// Pass the initial indicator data to an initialize function for the indicators.
idata.indicator_data.then( (data) => { this.init_indicators(data); } );
}
create_indicators(indicators, charts){
// loop through all the indicators received from the
// server and if the are enabled and create them.
for (let name in indicators) {
// If this indicator is hidden skip to the next one
if (!indicators[name].visible) {continue;}
// Get the type of indicator
let i_type = indicators[name].type;
// Call the indicator creation function
if (i_type == 'SMA') {
// The color of the line
let color = indicators[name].color;
this.indicators[name] = new SMA(name, charts.chart_1, color);
}
if (i_type == 'BOLBands') {
// The color of three lines
let color_u = bt_data.indicators[name].color_1;
let color_m = bt_data.indicators[name].color_2;
let color_l = bt_data.indicators[name].color_3;
this.indicators[name] = new Bolenger(name, charts.chart_1, color_u, color_m, color_l);
}
if (i_type == 'MACD') {
// The color of two lines
let color_m = bt_data.indicators[name].color_1;
let color_s = bt_data.indicators[name].color_2;
this.indicators[name] = new MACD(name, charts, color_m, color_s);
}
if (i_type == 'Volume') { this.indicators[name] = new Volume(name, charts.chart_1); }
if (i_type == 'ATR') { this.indicators[name] = new ATR(name); }
if (i_type == 'LREG') {
// The color of the line
let color = indicators[name].color;
this.indicators[name] = new Linear_Regression(name, charts.chart_1, color);
}
if (i_type == 'RSI') {
let color = indicators[name].color;
this.indicators[name] = new RSI(name, charts, color);
}
if (i_type == 'EMA') {
// The color of the line
let color = indicators[name].color;
this.indicators[name] = new EMA(name, charts.chart_1, color);
}
}
}
init_indicators(data){
// Loop through all the indicators.
for (name in data){
// Call the initialization function for each indicator.
this.indicators[name].init(data[name]['data']);
}
}
}