Polygon:
- Added async methods to polygon. - The `requests` library is no longer required, with `urllib` being used instead. - Added the `get_bar_data` function, which returns a dataframe of aggregate data from polygon. - Opened up the `subscribe` and `unsubscribe` functions Enhancements: - Tables will now scroll when the rows exceed table height. Bugs: - Fixed a bug preventing async functions being used with horizontal line event. - Fixed a bug causing the legend to show duplicate lines if the line was created after the legend. - Fixed a bug causing the line hide icon to persist within the legend after deletion (#75) - Fixed a bug causing the search box to be unfocused when the chart is loaded.
This commit is contained in:
@ -1,103 +1,113 @@
|
||||
function makeChart(innerWidth, innerHeight, autoSize=true) {
|
||||
let chart = {
|
||||
markers: [],
|
||||
horizontal_lines: [],
|
||||
lines: [],
|
||||
wrapper: document.createElement('div'),
|
||||
div: document.createElement('div'),
|
||||
scale: {
|
||||
width: innerWidth,
|
||||
height: innerHeight,
|
||||
},
|
||||
candleData: [],
|
||||
commandFunctions: [],
|
||||
precision: 2,
|
||||
}
|
||||
chart.chart = LightweightCharts.createChart(chart.div, {
|
||||
width: window.innerWidth*innerWidth,
|
||||
height: window.innerHeight*innerHeight,
|
||||
layout: {
|
||||
textColor: '#d1d4dc',
|
||||
background: {
|
||||
color:'#000000',
|
||||
type: LightweightCharts.ColorType.Solid,
|
||||
},
|
||||
fontSize: 12
|
||||
},
|
||||
rightPriceScale: {
|
||||
scaleMargins: {top: 0.3, bottom: 0.25},
|
||||
},
|
||||
timeScale: {timeVisible: true, secondsVisible: false},
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal,
|
||||
vertLine: {
|
||||
labelBackgroundColor: 'rgb(46, 46, 46)'
|
||||
},
|
||||
horzLine: {
|
||||
labelBackgroundColor: 'rgb(55, 55, 55)'
|
||||
if (!window.Chart) {
|
||||
class Chart {
|
||||
constructor(chartId, innerWidth, innerHeight, position, autoSize) {
|
||||
this.makeCandlestickSeries = this.makeCandlestickSeries.bind(this)
|
||||
this.reSize = this.reSize.bind(this)
|
||||
this.id = chartId
|
||||
this.lines = []
|
||||
this.wrapper = document.createElement('div')
|
||||
this.div = document.createElement('div')
|
||||
this.scale = {
|
||||
width: innerWidth,
|
||||
height: innerHeight,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
vertLines: {color: 'rgba(29, 30, 38, 5)'},
|
||||
horzLines: {color: 'rgba(29, 30, 58, 5)'},
|
||||
},
|
||||
handleScroll: {vertTouchDrag: true},
|
||||
})
|
||||
let up = 'rgba(39, 157, 130, 100)'
|
||||
let down = 'rgba(200, 97, 100, 100)'
|
||||
chart.series = chart.chart.addCandlestickSeries({
|
||||
color: 'rgb(0, 120, 255)', upColor: up, borderUpColor: up, wickUpColor: up,
|
||||
downColor: down, borderDownColor: down, wickDownColor: down, lineWidth: 2,
|
||||
})
|
||||
chart.volumeSeries = chart.chart.addHistogramSeries({
|
||||
color: '#26a69a',
|
||||
priceFormat: {type: 'volume'},
|
||||
priceScaleId: '',
|
||||
})
|
||||
chart.series.priceScale().applyOptions({
|
||||
scaleMargins: {top: 0.2, bottom: 0.2},
|
||||
});
|
||||
chart.volumeSeries.priceScale().applyOptions({
|
||||
scaleMargins: {top: 0.8, bottom: 0},
|
||||
});
|
||||
chart.wrapper.style.width = `${100*innerWidth}%`
|
||||
chart.wrapper.style.height = `${100*innerHeight}%`
|
||||
chart.wrapper.style.display = 'flex'
|
||||
chart.wrapper.style.flexDirection = 'column'
|
||||
chart.wrapper.style.position = 'relative'
|
||||
this.commandFunctions = []
|
||||
this.chart = LightweightCharts.createChart(this.div, {
|
||||
width: window.innerWidth * innerWidth,
|
||||
height: window.innerHeight * innerHeight,
|
||||
layout: {
|
||||
textColor: '#d1d4dc',
|
||||
background: {
|
||||
color: '#000000',
|
||||
type: LightweightCharts.ColorType.Solid,
|
||||
},
|
||||
fontSize: 12
|
||||
},
|
||||
rightPriceScale: {
|
||||
scaleMargins: {top: 0.3, bottom: 0.25},
|
||||
},
|
||||
timeScale: {timeVisible: true, secondsVisible: false},
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal,
|
||||
vertLine: {
|
||||
labelBackgroundColor: 'rgb(46, 46, 46)'
|
||||
},
|
||||
horzLine: {
|
||||
labelBackgroundColor: 'rgb(55, 55, 55)'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
vertLines: {color: 'rgba(29, 30, 38, 5)'},
|
||||
horzLines: {color: 'rgba(29, 30, 58, 5)'},
|
||||
},
|
||||
handleScroll: {vertTouchDrag: true},
|
||||
})
|
||||
this.wrapper.style.width = `${100 * innerWidth}%`
|
||||
this.wrapper.style.height = `${100 * innerHeight}%`
|
||||
this.wrapper.style.display = 'flex'
|
||||
this.wrapper.style.flexDirection = 'column'
|
||||
this.wrapper.style.position = 'relative'
|
||||
this.wrapper.style.float = position
|
||||
|
||||
chart.div.style.position = 'relative'
|
||||
chart.div.style.display = 'flex'
|
||||
chart.wrapper.appendChild(chart.div)
|
||||
document.getElementById('wrapper').append(chart.wrapper)
|
||||
this.div.style.position = 'relative'
|
||||
this.div.style.display = 'flex'
|
||||
this.wrapper.appendChild(this.div)
|
||||
document.getElementById('wrapper').append(this.wrapper)
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
for (let i=0; i<chart.commandFunctions.length; i++) {
|
||||
if (chart.commandFunctions[i](event)) break
|
||||
document.addEventListener('keydown', (event) => {
|
||||
for (let i = 0; i < this.commandFunctions.length; i++) {
|
||||
if (this.commandFunctions[i](event)) break
|
||||
}
|
||||
})
|
||||
if (!autoSize) return
|
||||
window.addEventListener('resize', () => this.reSize())
|
||||
}
|
||||
})
|
||||
reSize() {
|
||||
let topBarOffset = 'topBar' in this ? this.topBar.offsetHeight : 0
|
||||
this.chart.resize(window.innerWidth * this.scale.width, (window.innerHeight * this.scale.height) - topBarOffset)
|
||||
}
|
||||
makeCandlestickSeries() {
|
||||
this.markers = []
|
||||
this.horizontal_lines = []
|
||||
this.candleData = []
|
||||
this.precision = 2
|
||||
let up = 'rgba(39, 157, 130, 100)'
|
||||
let down = 'rgba(200, 97, 100, 100)'
|
||||
this.series = this.chart.addCandlestickSeries({
|
||||
color: 'rgb(0, 120, 255)', upColor: up, borderUpColor: up, wickUpColor: up,
|
||||
downColor: down, borderDownColor: down, wickDownColor: down, lineWidth: 2,
|
||||
})
|
||||
this.volumeSeries = this.chart.addHistogramSeries({
|
||||
color: '#26a69a',
|
||||
priceFormat: {type: 'volume'},
|
||||
priceScaleId: '',
|
||||
})
|
||||
this.series.priceScale().applyOptions({
|
||||
scaleMargins: {top: 0.2, bottom: 0.2},
|
||||
});
|
||||
this.volumeSeries.priceScale().applyOptions({
|
||||
scaleMargins: {top: 0.8, bottom: 0},
|
||||
});
|
||||
}
|
||||
toJSON() {
|
||||
// Exclude the chart attribute from serialization
|
||||
const {chart, ...serialized} = this;
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
window.Chart = Chart
|
||||
|
||||
if (!autoSize) return chart
|
||||
window.addEventListener('resize', () => reSize(chart))
|
||||
return chart
|
||||
}
|
||||
|
||||
function reSize(chart) {
|
||||
let topBarOffset = 'topBar' in chart ? chart.topBar.offsetHeight : 0
|
||||
chart.chart.resize(window.innerWidth*chart.scale.width, (window.innerHeight*chart.scale.height)-topBarOffset)
|
||||
}
|
||||
|
||||
if (!window.HorizontalLine) {
|
||||
class HorizontalLine {
|
||||
constructor(chart, lineId, price, color, width, style, axisLabelVisible, text) {
|
||||
this.updatePrice = this.updatePrice.bind(this)
|
||||
this.deleteLine = this.deleteLine.bind(this)
|
||||
this.chart = chart
|
||||
this.price = price
|
||||
this.color = color
|
||||
this.id = lineId
|
||||
this.priceLine = {
|
||||
price: this.price,
|
||||
color: color,
|
||||
color: this.color,
|
||||
lineWidth: width,
|
||||
lineStyle: style,
|
||||
axisLabelVisible: axisLabelVisible,
|
||||
@ -128,7 +138,8 @@ if (!window.HorizontalLine) {
|
||||
|
||||
updateColor(color) {
|
||||
this.chart.series.removePriceLine(this.line)
|
||||
this.priceLine.color = color
|
||||
this.color = color
|
||||
this.priceLine.color = this.color
|
||||
this.line = this.chart.series.createPriceLine(this.priceLine)
|
||||
}
|
||||
|
||||
@ -210,11 +221,7 @@ if (!window.HorizontalLine) {
|
||||
|
||||
makeLines(chart) {
|
||||
this.lines = []
|
||||
if (this.linesEnabled) {
|
||||
chart.lines.forEach((line) => {
|
||||
this.lines.push(this.makeLineRow(line))
|
||||
})
|
||||
}
|
||||
if (this.linesEnabled) chart.lines.forEach(line => this.lines.push(this.makeLineRow(line)))
|
||||
}
|
||||
|
||||
makeLineRow(line) {
|
||||
@ -322,39 +329,29 @@ function syncCrosshairs(childChart, parentChart) {
|
||||
childChart.subscribeCrosshairMove(childCrosshairHandler)
|
||||
}
|
||||
|
||||
function chartTimeToDate(stampOrBusiness) {
|
||||
if (typeof stampOrBusiness === 'number') {
|
||||
stampOrBusiness = new Date(stampOrBusiness*1000)
|
||||
}
|
||||
else if (typeof stampOrBusiness === 'string') {
|
||||
let [year, month, day] = stampOrBusiness.split('-').map(Number)
|
||||
stampOrBusiness = new Date(Date.UTC(year, month-1, day))
|
||||
}
|
||||
else {
|
||||
stampOrBusiness = new Date(Date.UTC(stampOrBusiness.year, stampOrBusiness.month - 1, stampOrBusiness.day))
|
||||
}
|
||||
return stampOrBusiness
|
||||
function stampToDate(stampOrBusiness) {
|
||||
return new Date(stampOrBusiness*1000)
|
||||
}
|
||||
function dateToStamp(date) {
|
||||
return Math.floor(date.getTime()/1000)
|
||||
}
|
||||
|
||||
function dateToChartTime(date, interval) {
|
||||
if (interval >= 24*60*60*1000) {
|
||||
return {day: date.getUTCDate(), month: date.getUTCMonth()+1, year: date.getUTCFullYear()}
|
||||
}
|
||||
return Math.floor(date.getTime()/1000)
|
||||
function lastBar(obj) {
|
||||
return obj[obj.length-1]
|
||||
}
|
||||
|
||||
function calculateTrendLine(startDate, startValue, endDate, endValue, interval, chart, ray=false) {
|
||||
let reversed = false
|
||||
if (chartTimeToDate(endDate).getTime() < chartTimeToDate(startDate).getTime()) {
|
||||
if (stampToDate(endDate).getTime() < stampToDate(startDate).getTime()) {
|
||||
reversed = true;
|
||||
[startDate, endDate] = [endDate, startDate];
|
||||
}
|
||||
let startIndex
|
||||
if (chartTimeToDate(startDate).getTime() < chartTimeToDate(chart.candleData[0].time).getTime()) {
|
||||
if (stampToDate(startDate).getTime() < stampToDate(chart.candleData[0].time).getTime()) {
|
||||
startIndex = 0
|
||||
}
|
||||
else {
|
||||
startIndex = chart.candleData.findIndex(item => chartTimeToDate(item.time).getTime() === chartTimeToDate(startDate).getTime())
|
||||
startIndex = chart.candleData.findIndex(item => stampToDate(item.time).getTime() === stampToDate(startDate).getTime())
|
||||
}
|
||||
|
||||
if (startIndex === -1) {
|
||||
@ -366,9 +363,9 @@ function calculateTrendLine(startDate, startValue, endDate, endValue, interval,
|
||||
startValue = endValue
|
||||
}
|
||||
else {
|
||||
endIndex = chart.candleData.findIndex(item => chartTimeToDate(item.time).getTime() === chartTimeToDate(endDate).getTime())
|
||||
endIndex = chart.candleData.findIndex(item => stampToDate(item.time).getTime() === stampToDate(endDate).getTime())
|
||||
if (endIndex === -1) {
|
||||
let barsBetween = (chartTimeToDate(endDate)-chartTimeToDate(chart.candleData[chart.candleData.length-1].time))/interval
|
||||
let barsBetween = (stampToDate(endDate)-stampToDate(chart.candleData[chart.candleData.length-1].time))/interval
|
||||
endIndex = chart.candleData.length-1+barsBetween
|
||||
}
|
||||
}
|
||||
@ -384,8 +381,7 @@ function calculateTrendLine(startDate, startValue, endDate, endValue, interval,
|
||||
}
|
||||
else {
|
||||
iPastData ++
|
||||
currentDate = dateToChartTime(new Date(chartTimeToDate(chart.candleData[chart.candleData.length-1].time).getTime()+(iPastData*interval)), interval)
|
||||
|
||||
currentDate = dateToStamp(new Date(stampToDate(chart.candleData[chart.candleData.length-1].time).getTime()+(iPastData*interval)))
|
||||
}
|
||||
|
||||
const currentValue = reversed ? startValue + rate_of_change * (numBars - i) : startValue + rate_of_change * i;
|
||||
@ -431,22 +427,23 @@ if (!window.ContextMenu) {
|
||||
active ? document.addEventListener('contextmenu', this.onRightClick) : document.removeEventListener('contextmenu', this.onRightClick)
|
||||
}
|
||||
menuItem(text, action, hover=false) {
|
||||
let item = document.createElement('div')
|
||||
let item = document.createElement('span')
|
||||
item.style.display = 'flex'
|
||||
item.style.alignItems = 'center'
|
||||
item.style.justifyContent = 'space-between'
|
||||
item.style.padding = '0px 10px'
|
||||
item.style.margin = '3px 0px'
|
||||
item.style.padding = '2px 10px'
|
||||
item.style.margin = '1px 0px'
|
||||
item.style.borderRadius = '3px'
|
||||
this.menu.appendChild(item)
|
||||
|
||||
let elem = document.createElement('div')
|
||||
let elem = document.createElement('span')
|
||||
elem.innerText = text
|
||||
item.appendChild(elem)
|
||||
|
||||
if (hover) {
|
||||
let arrow = document.createElement('div')
|
||||
arrow.innerHTML = `<svg width="15px" height="10px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.82054 20.7313C8.21107 21.1218 8.84423 21.1218 9.23476 20.7313L15.8792 14.0868C17.0505 12.9155 17.0508 11.0167 15.88 9.84497L9.3097 3.26958C8.91918 2.87905 8.28601 2.87905 7.89549 3.26958C7.50497 3.6601 7.50497 4.29327 7.89549 4.68379L14.4675 11.2558C14.8581 11.6464 14.8581 12.2795 14.4675 12.67L7.82054 19.317C7.43002 19.7076 7.43002 20.3407 7.82054 20.7313Z" fill="#fff"/></svg>`
|
||||
let arrow = document.createElement('span')
|
||||
arrow.innerText = `►`
|
||||
arrow.style.fontSize = '8px'
|
||||
item.appendChild(arrow)
|
||||
}
|
||||
|
||||
@ -457,13 +454,17 @@ if (!window.ContextMenu) {
|
||||
})
|
||||
elem.addEventListener('mouseout', (event) => item.style.backgroundColor = 'transparent')
|
||||
if (!hover) elem.addEventListener('click', (event) => {action(event); this.menu.style.display = 'none'})
|
||||
else elem.addEventListener('mouseover', () => action(item.getBoundingClientRect()))
|
||||
else {
|
||||
let timeout
|
||||
elem.addEventListener('mouseover', () => timeout = setTimeout(() => action(item.getBoundingClientRect()), 100))
|
||||
elem.addEventListener('mouseout', () => clearTimeout(timeout))
|
||||
}
|
||||
}
|
||||
separator() {
|
||||
let separator = document.createElement('div')
|
||||
separator.style.width = '90%'
|
||||
separator.style.height = '1px'
|
||||
separator.style.margin = '4px 0px'
|
||||
separator.style.margin = '3px 0px'
|
||||
separator.style.backgroundColor = '#3C434C'
|
||||
this.menu.appendChild(separator)
|
||||
}
|
||||
@ -471,3 +472,5 @@ if (!window.ContextMenu) {
|
||||
}
|
||||
window.ContextMenu = ContextMenu
|
||||
}
|
||||
|
||||
window.callbackFunction = () => undefined;
|
||||
Reference in New Issue
Block a user