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

This commit is contained in:
Rob 2022-05-11 00:10:39 -03:00
parent 5b2cfe31e9
commit 626fa9a1a6
9 changed files with 637 additions and 247 deletions

View File

@ -1,5 +1,5 @@
chart_configuration:
chart_interval: 4h
chart_interval: 1m
trading_pair: BTCUSDT
indicator_list:
ATR:
@ -39,7 +39,7 @@ indicator_list:
period: 100
type: LREG
value: '38139.51'
visible: 'True'
visible: true
MACD:
color_1: '#50d617'
color_2: '#94f657'

View File

@ -1,130 +0,0 @@
var app_con;
//*******************Chart*********************
//Reference the target div for the chart. Div was defined in index.html
var container = document.getElementById('chart');
//Create a chart object
var chart = LightweightCharts.createChart(container, {
width: 1000,
height: 500,
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
});
bind_charts(chart);
chart.applyOptions({
watermark: {visible: true,
color: '#DBC29E',
text: bt_data['trading_pair'],
fontSize: 30,
fontFamily: 'Roboto',
fontStyle: 'bold',
vertAlign: 'center'
}
});
// - Create the candle stick series for our chart
var candleSeries = chart.addCandlestickSeries();
//Fetch price history
var price_history = fetch('http://localhost:5000/history')
.then((r) => r.json())
.then((response) => {
return response;
})
//Initialise the candlestick series
price_history.then((ph) => {
//Initialise the candle data
candleSeries.setData(ph);
//Initialise indicators
indicator_init();
})
/* Place functions here that need to
be run everytime a new msg is received */
function update_on_msg(new_candle){
// Update candlestick series
candleSeries.update(new_candle);
// Update javascript coded indicators
indicator_update_msg_received(new_candle);
// Send a copy of the data to the server
app_con.send( JSON.stringify({ message_type: "candle_data", data :new_candle }));
}
/* Place functions that here that need to
be run everytime a candle is closed */
function update_on_candle_close(new_candle){
// Send a copy of the data to the server
app_con.send( JSON.stringify({ message_type: "candle_data", data :new_candle }));
}
// Create a web socket connection to the exchange
function set_websocket(interval){
// Connect to our app
app_con = new WebSocket('ws://localhost:5000/ws');
app_con.onopen = () => app_con.send("Connection OK");
app_con.addEventListener('message', ev => {
if(ev.data){
// Get the message received from server
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);
indicator_update(msg.data)
}
}
}
})
var ws = "wss://stream.binance.com:9443/ws/btcusdt@kline_" + interval;
var binanceSocket = new WebSocket(ws);
// Set the on-message call-back for the socket
binanceSocket.onmessage = function (event) {
// Convert message to json obj
var message = JSON.parse(event.data);
// Isolate the candle data from message
var candlestick = message.k;
//console.log(message.k)
// Reformat data for lightweight charts
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
update_on_msg(new_candle);
// Only call if the new candle received a new time stamp.
// Update the price history and per candle updated objects.
price_history.then((ph) => {
if ( new_candle.time > ph[ph.length-1].time) {
ph.push(new_candle);
update_on_candle_close(new_candle);
}
});
}
}

View File

@ -9,9 +9,8 @@ class Charts {
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=[];
number of charts with their position and zoom factors bound.*/
this.bound_charts=[];
// Only the main chart is created by default.
this.create_main_chart();
}
@ -36,6 +35,10 @@ class Charts {
this.bind_charts(this.chart_1);
}
update_main_chart(new_candle){
// Update candlestick series
this.candleSeries.update(new_candle);
}
create_RSI_chart(){
this.chart2 = this.create_chart(this.chart2_id, 100);
@ -111,7 +114,7 @@ class Charts {
// 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;
let bcl = Object.keys(this.bound_charts).length;
// if bound_charts has two element in it bind them
if (bcl == 2) { this.bind2charts(); }
@ -122,65 +125,66 @@ class Charts {
}
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);
if ( !this.bound_charts.includes(chart) ){
this.bound_charts.push(chart);
}
}
bind2charts(){
//On change in chart 1 change chart 2
window.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler1);
function syncHandler1(e) {
let 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();
let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to 2nd chart.
window.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
}
this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler1);
//On change in chart 2 change chart 1
window.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
function syncHandler2(e) {
let 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();
let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
// Apply barSpacing(zoom) and position to chart 1
window.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
}
bind3charts(){
//On change to chart 1 change chart 2 and 3
window.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler);
function syncHandler(e) {
let 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();
let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
let scrollPosition1 = this.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 });
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
this.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
}
this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncHandler);
//On change to chart 2 change chart 1 and 3
window.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
function syncHandler2(e) {
let 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();
let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
let scrollPosition2 = this.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 });
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
this.bound_charts[2].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncHandler2);
//On change to chart 3 change chart 1 and 2
window.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncHandler3);
function syncHandler3(e) {
let 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();
let barSpacing2 = this.bound_charts[2].timeScale().getBarSpacing();
let scrollPosition2 = this.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 });
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
}
this.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncHandler3);
}

View File

@ -1,26 +1,35 @@
class Communication {
constructor(interval, ocu, occ, oiu) {
class Comms {
constructor(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){
// Call the callback provided.
this.on_candle_update(new_candle);
}
candle_close(new_candle){
// Send a copy of the data to the server
// Forward a copy of the new candle to the server.
this.app_con.send( JSON.stringify({ message_type: "candle_data", data :new_candle }));
// Call the callback provided.
this.on_candle_close(new_candle);
}
indicator_update(data){
this.this.on_indctr_update(data);
// Call the callback provided.
this.on_indctr_update(data);
}
getPriceHistory(){
let ph = fetch('http://localhost:5000/history').then((r) => r.json()).then( (data) => { return data; } );
return ph;
}
getIndicatorData(){
let id = fetch('http://localhost:5000/indicator_init').then((r) => r.json()).then( (data) => { return data; } );
return id;
}
set_app_con(){
@ -42,7 +51,6 @@ class Communication {
if (msg.reply) {
// Handle indicator updates
if (msg.reply == 'i_updates'){
// console.log(msg.data);
this.indicator_update(msg.data)
}
}
@ -60,7 +68,6 @@ class Communication {
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,
@ -73,7 +80,7 @@ class Communication {
// Update frequently updated objects
this.candle_update(new_candle);
// Update less frequently updated objects.
if (candlestick.x == true) { this.candle.close(new_candle); }
if (candlestick.x == true) { this.candle_close(new_candle); }
}
}
}

30
static/controls.js vendored Normal file
View File

@ -0,0 +1,30 @@
class Controls {
constructor() {
this.name = 'controls';
}
showAtPos(event, id) {
// Function to display popup output at curser.
// Used in the chart controls to enable/disable indicators.
var el, x, y;
el = document.getElementById(id);
if (window.event) {
x = window.event.clientX + document.documentElement.scrollLeft
+ document.body.scrollLeft;
y = window.event.clientY + document.documentElement.scrollTop +
+ document.body.scrollTop;
}
else {
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
}
// The popup hides on mouse out so the element is moved back a little to
// ensure the mouse is still hovering over it when it gets displayed
y = y - (10);
x = x - (15);
el.style.left = x + "px";
el.style.top = y + "px";
el.style.display = "block";
}
}

View File

@ -1,22 +1,55 @@
class Data {
constructor() {
// Set the id's of the HTML elements that contain each section of the user interface.
// The id's of the HTML elements that contain sections 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.
/* Set into memory configuration data from the server. */
// The assets being traded.
this.trading_pair = bt_data.trading_pair;
// The time period of the charts.
this.interval = bt_data.interval;
this.price_history = fetch('http://localhost:5000/history').then((r) => r.json()).then( (data) => { return data; } );
// All the indicators available.
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');
/* Comms handles communication with the servers. Pass it
a list of callbacks to handle various incoming messages.*/
this.comms = new Comms(this.candle_update, this.candle_close, this.indicator_update);
// Open the connection to our local server.
this.comms.set_app_con();
/* Open connection for streaming candle data wth the exchange.
Pass it the time period of candles to stream. */
this.comms.set_exchange_con(this.interval);
//Request historical price data from the server.
this.price_history = this.comms.getPriceHistory();
// Request from the server initialization data for the indicators.
this.indicator_data = this.comms.getIndicatorData();
// Call back for indicator updates.
this.i_updates = null;
}
candle_close(){
console.log('candle close');
candle_update(new_candle){
// This is called everytime a candle update comes from the local server.
window.UI.charts.update_main_chart(new_candle);
//console.log('Candle update:');
//console.log(new_candle);
}
set_i_updates(call_back){
this.i_updates = call_back;
}
indicator_update(data){
// This is called everytime an indicator update come in.
window.UI.data.i_updates(data);
}
candle_close(new_candle){
// This is called everytime a candle closes.
//console.log('Candle close:');
//console.log(new_candle);
}
}

View File

@ -10,13 +10,6 @@
// this.height = height;
// }
//}
//
//class Controls {
// constructor() {
// this.height = height;
// }
//}
//
//class Strategies {
// constructor() {
// this.height = height;
@ -53,19 +46,14 @@
// }
//}
//
//class Indicator_Output {
// constructor() {
// this.height = height;
// }
//}
class User_Interface{
/* This contains the entire User interface.*/
/* This contains all the code for our User interface.
The code is separated into classes that maintain and
provide the data and functionality of each panel of
the UI. */
constructor() {
/* Create the objects that contain all the
data and scripts required for each section of
the User interface. */
/* Data object is responsible for fetching and maintaining
up-to-date configurable and variable data for the UI */
@ -82,9 +70,8 @@ class User_Interface{
}
this.charts = new Charts(chart_init_data);
/* The Indicators object is responsible for maintaining and
interacting with the indicator section. As well as
updating the display on the charts.*/
/* The Indicators object is responsible for interactions with
the edit indicator menu and updating the display on the charts.*/
let ind_init_data = {
indicators: this.data.indicators,
indicator_data: this.data.indicator_data
@ -92,12 +79,9 @@ class User_Interface{
/* Pass the initialization for the indicators and a reference to
the charts object so the indicators can update it directly.*/
this.indicators = new Indicators(this.charts, ind_init_data);
this.communicate = new Communication(
this.data.interval,
this.data.candle_update,
this.data.candle_close,
this.indicators.update);
this.data.set_i_updates(this.indicators.update);
/* The object that handles the interface controls.*/
this.controls = new Controls();
}
}
UI = new User_Interface();

View File

@ -1,3 +1,34 @@
class Indicator_Output {
constructor(name) {
this.legend={};
}
create_legend(name, chart, lineSeries){
// Create legend div and append it to the output element
let target_div = document.getElementById('indicator_output');
this.legend[name] = document.createElement('div');
this.legend[name].className = 'legend';
target_div.appendChild(this.legend[name]);
this.legend[name].style.display = 'block';
this.legend[name].style.left = 3 + 'px';
this.legend[name].style.top = 3 + 'px';
// subscribe set legend text to crosshair moves
chart.subscribeCrosshairMove((param) => {
this.set_legend_text(param.seriesPrices.get(lineSeries),name);
});
}
set_legend_text(priceValue,name) {
// Callback assigned to fire on crosshair movements.
let val = 'n/a';
if (priceValue !== undefined) {
val = (Math.round(priceValue * 100) / 100).toFixed(2);
}
this.legend[name].innerHTML = name + ' <span style="color:rgba(4, 111, 232, 1)">' + val + '</span>';
}
}
iOutput = new Indicator_Output();
class Indicator {
constructor(name) {
// The name of the indicator.
@ -29,6 +60,9 @@ class Indicator {
color: color,
lineWidth: lineWidth
});
//Initialise the crosshair legend for the charts.
iOutput.create_legend(this.name, chart, this.lines[name]);
}
setLine(name, data, value_name){
// Initialize the data with the data object provided.
@ -36,13 +70,13 @@ class Indicator {
// Isolate the last value provided and round to 2 decimals places.
let priceValue = data.at(-1).value;
this.updateDisplay(name, priceValue, value_name);
// Update indicator output/crosshair legend.
iOutput.set_legend_text(data.at(-1).value, this.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);
@ -50,8 +84,10 @@ class Indicator {
updateLine(name, data, value_name){
// Update the line-set data in the chart
this.lines[name].update(data);
// Update indicator output/crosshair legend.
iOutput.set_legend_text(data.value, this.name);
// Update the data in the edit and view indicators panel
this.updateDisplay(name, data, value_name);
this.updateDisplay(name, data.value, value_name);
}
updateHist(name,data){
this.hist[name].update(data);
@ -66,14 +102,12 @@ class SMA extends Indicator{
// 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');
this.updateLine('line', data[0], 'value');
}
}
@ -87,20 +121,17 @@ 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 the chart doesn't exist 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');
this.updateLine('line', data[0], 'value');
}
}
@ -108,7 +139,7 @@ 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 the chart doesn't exist create one.
if( !charts.hasOwnProperty('chart3') ) { charts.create_MACD_chart(); }
let chart = charts.chart3;
@ -116,19 +147,18 @@ class MACD extends Indicator{
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]);
this.updateLine('line_m', data[0][0], 'macd');
this.updateLine('line_s', data[1][0], 'signal');
this.updateHist(name, data[2][0]);
}
}
@ -137,7 +167,7 @@ class ATR extends Indicator{
this.updateDisplay(this.name, data.at(-1).value, 'value');
}
update(data) {
this.updateDisplay(this.name, data.value, 'value');
this.updateDisplay(this.name, data[0].value, 'value');
}
}
@ -153,7 +183,7 @@ class Volume extends Indicator{
this.setHist(this.name, data);
}
update(data){
this.updateHist(this.name, data);
this.updateHist(this.name, data[0]);
}
}
@ -177,11 +207,11 @@ class Bolenger extends Indicator{
update(data){
// Update the line-set data in the chart
this.line_u.update(data[0], 'value1');
this.updateLine('line_u', data[0][0], 'value1');
// Update the line-set data in the chart
this.line_m.update(data[1]), 'value2';
this.updateLine('line_m', data[1][0], 'value2');
// Update the line-set data in the chart
this.line_l.update(data[2], 'value3');
this.updateLine('line_l', data[2][0], 'value3');
}
}
@ -189,11 +219,11 @@ class Bolenger extends Indicator{
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
this.i_objs = {};
// Pass a list of indicators that was 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.
// Pass the initial indicator data to a function to initialize the indicators.
idata.indicator_data.then( (data) => { this.init_indicators(data); } );
@ -213,36 +243,36 @@ class Indicators {
if (i_type == 'SMA') {
// The color of the line
let color = indicators[name].color;
this.indicators[name] = new SMA(name, charts.chart_1, color);
this.i_objs[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);
this.i_objs[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);
this.i_objs[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 == 'Volume') { this.i_objs[name] = new Volume(name, charts.chart_1); }
if (i_type == 'ATR') { this.i_objs[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);
this.i_objs[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);
this.i_objs[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);
this.i_objs[name] = new EMA(name, charts.chart_1, color);
}
}
}
@ -250,9 +280,36 @@ class Indicators {
// Loop through all the indicators.
for (name in data){
// Call the initialization function for each indicator.
this.indicators[name].init(data[name]['data']);
this.i_objs[name].init(data[name]['data']);
}
}
update(updates){
for (name in updates){
window.UI.indicators.i_objs[name].update(updates[name].data);
}
}
add_to_list(){
// Adds user input to a list and displays it in a HTML element.
// Used in the Create indicator panel (!)Called from inline html.
var n = document.getElementById("new_prop_name").value;
var v = document.getElementById("new_prop_value").value;
p={};
p[n] = v;
if (document.getElementById("new_prop_list").innerHTML ==""){
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', JSON.stringify(p));
}else{
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); }
}
submit_new_i(){
/* Populates a hidden <input> with a value from another element then submits the form
Used in the create indicator panel.*/
pl=document.getElementById("new_prop_list").innerHTML;
if(pl) { pl = '[' + pl + ']'; }
document.getElementById("new_prop_obj").value=pl;
document.getElementById("new_i_form").submit();
}
}

405
templates/index.html Normal file
View File

@ -0,0 +1,405 @@
<html>
<head>
<!-- Load style sheets and set the title -->
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='brighterStyles.css') }}">
<title>{{ title }}</title>
<!-- Load lightweight charts -->
<script src="{{ url_for('static', filename='lightweight-charts.standalone.production.js') }}"></script>
<!-- The server places initiation data in the HTML. This loads it into the DOM. -->
<script type="text/javascript">
function get_init_data(vars) {return vars}
bt_data = get_init_data({{js_data|tojson}});
</script>
<!-- Load javascript -->
<script src="{{ url_for('static', filename='data.js') }}"></script>
<script src="{{ url_for('static', filename='indicators.js') }}"></script>
<script src="{{ url_for('static', filename='charts.js') }}"></script>
<script src="{{ url_for('static', filename='communication.js') }}"></script>
<script src="{{ url_for('static', filename='controls.js') }}"></script>
<script>function fill_prop(){let a=1;}</script>
</head>
<body>
<!-- Pop up forms for interface -->
<div class="form-popup" id="new_sig_form">
<form action="/new_signal" class="form-container">
<!-- Panel 1 of 3 (5 rows, 2 columns) -->
<div id="panel_1" class="form_panels" style="grid-template-columns:repeat(2,1fr);grid-template-rows: repeat(5,1fr);">
<!-- Panel title (row 1/5)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Add New Signal</h1>
<!-- Name Input field (row 2/5)-->
<div id = "SigName_div" style="grid-column: 1 / span 2; grid_row:2;">
<label for='signal_name' >Signal Name:</label>
<input type="text" id="signal_name" name="signal_name" />
</div>
<!-- Source Input field (row 3/5)-->
<label for="sig_source" style="grid-column: 1; grid-row: 3;"><b>Signal source</b></label>
<select name="sig_source" id="sig_source" style="grid-column: 2; grid-row: 3;" onchange= "fill_prop('sig_prop', this.value)">
<!-- Jinja2 loop through and populate the options -->
<!-- Uses namespace to set a var in global scope, so we can pass info to javascript later. -->
{% set ns = namespace(optonVal=0) %}
{% for each in indicator_list %}
<option>{{each}}</option>
{% if loop.index0 == 0 %}
{% set ns.optonVal = each %}
{% endif %}
{% endfor %}
</select>
<!-- Property Input field (row 4/5)-->
<label style="grid-column: 1; grid-row: 4;" for="sig_prop"><b>Property</b></label>
<select style="grid-column: 2; grid-row: 4;" id="sig_prop" name="sig_prop" >
</select>
<script>fill_prop('sig_prop', '{{ns.optonVal}}')</script>
<!-- Input controls (row 5/5)-->
<div style="grid-column: 1 / span 2; grid-row: 5;">
<button type="button" class="btn cancel" onclick="close_signal_Form()">Close</button>
<button type="button" class="btn next" onclick="ns_next(1)">Next</button>
</div>
</div><!----End panel 1--------->
<!-- Panel 2 of 3 (6 rows, 2 columns) -->
<div id="panel_2" class="form_panels" style="grid-template-columns:repeat(2,1fr);grid-template-rows: 1fr 35px 35px repeat(3,1fr);">
<!-- Panel title(Signal_type) (row 1/6)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Signal Type</h1>
<!-- Type Input field (row 2/6) -->
<div id = "Signal_type" style="grid-column: 1 / span 2; grid_row:2;">
<label for="signal_type"><b>Signal Type:</b></label>
<select name="signal_type" id="select_s_type" onchange="hideIfTrue(this.value,'Value','subpanel_1');hideIfTrue(this.value,'Comparison','subpanel_2');">
<option>Value</option>
<option>Comparison</option>
</select>
</div>
<!-- display signal (row 3/6) -->
<span id="sig_display" style="grid-column: 1 / span 2; grid_row: 3;">name:{property}(operator)name:{property}</span>
<!-- Operator field (row 4/6) -->
<div id="sig_operator" style="grid-column: 1 / span 2; grid_row: 4;">
<input type = "radio" id="gt" name="Operator" value=">">
<label for = "gt">Greater than</label>
<input type="radio" id="lt" name="Operator" value="<">
<label for = "lt">lessthan</label><br>
<input type = "radio" id="eq" name="Operator" value="=" checked="checked">
<label for = "eq">Equals</label>
<input type="radio" id="within" name="Operator" value="+/-">
<label for = "within">Within range</label>
<input type="range" id="rangeSlider" min="0" max="1000" value="100" onchange="document.getElementById('rangeVal').value=this.value;">
<input type="text" id="rangeVal" value="100" onchange="document.getElementById('rangeSlider').value=this.value;"/>
</div>
<!-- Compare sub-panel (row 5,6) -->
<div id="subpanel_2" style="grid-column: 1 / span 2; grid_row: 5 / span 3;">
<label for="value">Value:</label>
<input id="value" name="value" type="number" value="100"/>
</div>
<div id="subpanel_1" style="grid-column: 1 / span 2; grid_row: 5 / span 3;">
<!-- Source Input field -->
<label for="sig2_source" ><b>Signal source</b></label>
<select name="sig2_source" id="sig2_source" onchange= "fill_prop('sig2_prop', this.value)">
<!-- Jinja2 loop through and populate the options -->
<!-- Uses namespace to set a var in global scope, so we can pass info to javascript later. -->
{% set ns = namespace(optonVal=0) %}
{% for each in indicator_list %}
<option>{{each}}</option>
{% if loop.index0 == 0 %}
{% set ns.optonVal = each %}
{% endif %}
{% endfor %}
</select><br>
<!-- Property Input field -->
<label for="sig2_prop"><b>Property</b></label>
<select id="sig2_prop" name="sig2_prop" >
</select>
<script>fill_prop('sig2_prop', '{{ns.optonVal}}')</script>
</div>
<!-- Input controls (row 6/6) -->
<div class="padDiv" style="grid-column: 1/3; grid-row: 6;">
<button type="button" class="btn" onclick="switch_panel('panel_2','panel_1')">Back</button>
<button type="button" class="btn submit" onclick="ns_next(2)">Next</button>
</div>
</div><!----End panel 2--------->
<!-- Panel 3 of 3 (6 rows, 2 columns) -->
<div id="panel_3" class="form_panels" style="grid-template-columns:repeat(2,1fr);grid-template-rows: 1fr 35px 35px repeat(3,1fr);">
<!-- Panel title(Create Signal) (row 1/6)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create Signal</h1>
<!-- display signal (row 2/6) -->
<span id="sig_display2" style="grid-column: 1 / span 2; grid_row: 2;"></span>
<!-- display current Values (row 3/6) -->
<span style="grid-column: 1 / span 2; grid_row: 3;">Current values</span>
<!-- display realtime values (row 4/6) -->
<span id="sig_realtime" style="grid-column: 1 / span 2; grid_row: 4;"></span>
<!-- display current evaluation (row 4/6) -->
<span style="grid-column: 1; grid_row: 4;">Currently evaluates to :</span>
<span id="sig_eval" style="grid-column: 2; grid_row: 4;"></span>
<!-- Input controls (row 6/6) -->
<div class="padDiv" style="grid-column: 1/3; grid-row: 6;">
<button type="button" class="btn" onclick="switch_panel('panel_3','panel_2')">Back</button>
<button type="button" class="btn submit" onclick="submitNewSignal()">Next</button>
</div>
</div><!----End panel 3--------->
</form>
</div>
<!-- End pop up -->
<!-- Hidden Div elements containing markup for popup context menus.-->
<div id='indicators' onmouseleave="document.getElementById('indicators').style.display = 'none' ">
<form action="/settings" method="post">
<input type="hidden" name="setting" value="toggle_indicator"/>
{% for indicator in indicator_list %}
<input type="checkbox" id="{{ indicator }}" name="{{ indicator }}" value="indicator"{% if indicator in checked %} checked{%endif%}>
<label for = "{{ indicator }}">{{ indicator }}</label><br>
{% endfor %}
<input type="submit" value="Submit Changes">
</form>
</div>
<!-- Container for the whole user interface -->
<div id="master_panel" class="master_panel">
<!-- Chart Header -->
<div id="app_header" name="app_header">
<H1 id="app_title">{{ title }}</H1>
</div><!-- end Chart Header -->
<!-- Container for the javascript chart -->
<div id="chart">
<div class="a1" >
<!-- Chart specific controls -->
<div id="chart_controls">
<!-- Container target for any indicator output -->
<div id="indicator_output" ></div>
<!-- Time interval selector -->
<form action="/settings" method="post">
<input type="hidden" name="setting" value="interval" />
<select id="timeframe" name="timeframe" selected="{{interval_state}}" onchange="this.form.submit()">
{% for time_frame in intervals %}
<option {% if time_frame == interval_state %} selected="selected" {%endif%}>{{ time_frame }}</option>
{% endfor %}
</select>
</form>
<!-- Toggle On/Off indicators-->
<button id="enable_indicators" type="button" onclick="UI.controls.showAtPos(event,'indicators')">Indicators</button>
</div>
</div>
</div>
<!-- Indicator charts -->
<div id="chart2"></div>
<div id="chart3"></div>
<!-- Right Panel -->
<div id="right_panel">
<button class="collapsible bg_red" id="alerts_btn">Alerts</button>
<div class="content">
<p>No Alerts</p>
</div>
<button class="collapsible bg_blue">Exchange Info</button>
<div id="bal_content" class="content">
<div>
<h3>Status</h3>
<span id="exc_status">Not connected</span>
</div>
<div>
<h3>Balances</h3>
<div id="balances">
<div id="balances_tbl">
<table style="width:25%;">
{% for balance in my_balances %}
<tr><td>Asset</td><td>Balance</td><td>Profit&Loss</td></tr>
<tr>
<td>
{{ balance['asset'] }}
</td>
<td>
{{ balance['crossWalletBalance'] }}
</td>
<td>
{{ balance['crossUnPnl'] }}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
<div>
<h3>Active Tades</h3>
<span>None</span>
</div>
<div>
<h3>Open orders</h3>
<span>None</span>
</div>
</div>
<button class="collapsible bg_blue">Signals</button>
<div class="content">
<button class="new_btn" id="new_signal" onclick="open_signal_Form()">New Signal</button>
<hr>
<h3>Signals</h3>
</div>
<button class="collapsible bg_blue">Strategies</button>
<div class="content">
<button class="new_btn">New Strategy</button>
<hr>
<h3>Strategies</h3>
</div>
<button class="collapsible bg_blue">Statistics</button>
<div class="content">
<h3>Statistics</h3>
</div>
<button class="collapsible bg_blue">Backtesting</button>
<div class="content">
<h3>Back Testing</h3>
</div>
<button class="collapsible bg_blue">Trade</button>
<div class="content">
<div id="trade_content" class="cp_content">
<form action="/trade" method="post">
<input type="text" id="quantity" name="quantity" placeholder="eg. 0.001" />
<select id="symbol" name="symbol">
{% for symbol in symbols %}
<option>{{ symbol['symbol'] }}</option>
{% endfor %}
</select>
<input type="submit" name="buy" value="buy" />
<input type="submit" name="sell" value="sell" />
</form>
</div>
</div>
<script>
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
</script>
</div> <!-- End RightPanel -->
<!-- Edit Indicator Panel -->
<div id="edit_indcr_panel">
<!-- Edit Indicator Heading -->
<div id="edit_indcr_head">
<div id="h_name">Indicator Name</div>
<div id="h_properties">Properties</div>
</div>
<hr>
<!-- Edit Indicator Rows of individual forms to edit each indicator --!>
{% for indicator in indicator_list %}
<form action="/settings" method="post" >
<input type="hidden" name="setting" value="edit_indicator"/>
<div class="ierow">
<div id="edit_indctr_controls">
<button type="submit" name="delete" class="e_btn" value="{{indicator}}">&#10008;</button>
<button type="submit" name="submit" style ="color:darkgreen;" class="e_btn" value="{{indicator}}">&#x2714;</button>
</div>
<div class="iename">{{indicator}}</div>
<div id="ieprop_container">
{% for property in indicator_list[indicator] %}
{% set list1 = property.split('_') %}
<div class="ieprop" >
<label class="ietextbox" for="{{indicator}}_{{property}}">{{property}}</label>
{% if property=='type' %}
<select class="ietextbox" id="{{indicator}}_{{property}}" name="{{property}}">
{% for i_type in indicator_types['simple_indicators'] %}
<option value="{{i_type}}" {% if indicator_list[indicator][property] == i_type %} selected="selected"{%endif%}>{{i_type}}</option>
{% endfor %}
{% for i_type in indicator_types['other'] %}
<option value="{{i_type}}" {% if indicator_list[indicator][property] == i_type %} selected="selected"{%endif%}>{{i_type}}</option>
{% endfor %}
</select>
{% elif property=='ma' %}
<select class="ietextbox" id="{{indicator}}_{{property}}" name="{{property}}">
{% for ma_val in ma_vals %}
<option value="{{ma_vals[ma_val]}}" {% if indicator_list[indicator][property] == ma_vals[ma_val] %} selected="selected"{%endif%}>{{ma_val}}</option>
{% endfor %}
</select>
{% elif property=='color' or list1[0]=='color' %}
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{% elif property=='period' %}
<input class="ietextbox" type="number" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{% elif property=='visible' %}
<input class="ietextbox" type="checkbox" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}"
{% if indicator in checked %} checked{%endif%}>
{% elif property=='value' %}
<input class="ie_value" type="number" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}" readonly>
{% else %}
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{%endif%}
</div><!-- end row of properties --!>
{% endfor %}
</div><!-- end row of property container --!>
</div><!-- end of row container --!>
</form>
{% endfor %}
<!-- End of Rows of individual forms to edit each indicator --!>
<form action="/settings" method="post" id="new_i_form">
<input type="hidden" name="setting" value="new_indicator"/>
<hr>
<!-- Create indicator div container --!>
<div id="create_indcr_container">
<h1>Add Indicator</h1>
<label for "newi_name">Indicator Name</label><input type ="text" name="newi_name" value="New Indicator">
<label for "newi_type">Type</label>
<select id="newi_type" name="newi_type">
{% for i_type in indicator_types['simple_indicators'] %}
<option value="{{i_type}}">{{i_type}}</option>
{% endfor %}
{% for i_type in indicator_types['other'] %}
<option value="{{i_type}}">{{i_type}}</option>
{% endfor %}
</select>
<br><br>
<span>Properties: <span id="new_prop_list"></span></span>
<button name="add_prop_clear" id="add_prop_clear" style="color:darkred;" type="button" value="add_prop_clear" onclick="document.getElementById('new_prop_list').innerHTML=''">&#10008;</button>
<br><br>
<div id="add_prop_container" name="add_prop_container">
<label for "new_prop_name">Property Name</label>
<input type="text" id="new_prop_name" name="new_prop_name" value="name">
<br>
<label for "new_prop_value">Property Value</label>
<input type="text" id="new_prop_value" name="new_prop_value" value="value">
<button name="add_prop" id="add_prop" type="button" value="add_prop" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">&#10009;</button>
<label for "add_prop">Add Property</label>
</div>
<br>
<button type="button" onclick="UI.indicators.submit_new_i()" name="create_indicator" id="create_indicator" >Create New Indicator</button>
<input type="hidden" id="new_prop_obj" name="new_prop_obj" value="">
</div> <!-- End of Create indicator div container --!>
</form>
</div><!-- End of Edit Indicator Panel -->
</div><!-- End Master Panel --->
<!-- TODO ANY REASON THIS IS IN LINE WITH HTML?
<script src="{{ url_for('static', filename='chart.js') }}"></script>
<script type="module" >set_websocket( "{{interval_state}}" );</script>-->
<script src="{{ url_for('static', filename='general.js') }}"></script>
</body>
</html>