Skip to content

Instantly share code, notes, and snippets.

@jtryan
Forked from arlenner/PriceAxisVolumeDelta.js
Created September 25, 2022 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jtryan/1e5452d8b8fb2803eb3ae1e497517944 to your computer and use it in GitHub Desktop.
Save jtryan/1e5452d8b8fb2803eb3ae1e497517944 to your computer and use it in GitHub Desktop.
Price Axis Volume Delta indicator for the Tradovate platform.
const predef = require("./tools/predef");
const { min, max, du, px, op } = require('./tools/graphics')
class PriceAxisDelta {
init() {
this.byPrice = {}
this.openDate = new Date()
if(this.props.startsYesterday) this.openDate.setTime(Date.now() - 1000*60*60*24)
this.openDate.setHours(this.props.marketOpenHours, this.props.marketOpenMinutes)
this.closeDate = new Date()
if(this.props.closesTomorrow) this.closeDate.setTime(Date.now() + 1000*60*60*24)
this.closeDate.setHours(this.props.marketCloseHours, this.props.marketCloseMinutes)
}
map(d) {
if(d.date < this.openDate) {
return {}
}
if(d.date > this.closeDate) {
return {}
}
const combinedBars = {
tag: 'Container',
key: 'combinedBars',
origin: {
cs: 'grid',
h: 'right',
v: 'top'
},
global: true,
children: [],
conditions: {
scaleRangeY: {
max: 3
}
}
}
const combinedText = {
tag: 'Container',
key: 'combinedText',
origin: {
cs: 'grid',
h: 'right',
v: 'top'
},
global: true,
children: [],
conditions: {
scaleRangeY: {
max: 3,
min: 10/this.props.priceLevelsPerBar
}
}
}
const textContainer = {
tag: 'Container',
key: 'PADV_textcontainer',
origin: {
cs: 'grid',
h: 'right',
v: 'top'
},
global: true,
children: [],
conditions: {
scaleRangeY: {
min: 12
}
}
}
const barsContainer = {
tag: 'Container',
key: 'PADV_barscontainer',
global: true,
origin: {
cs: 'grid',
h: 'right',
v: 'top'
},
children: [],
conditions: {
scaleRangeY: {
min: 3
}
}
}
d.profile().forEach((profile, i, arr) => {
const { price, bidVol, askVol, vol } = profile
let priceKey = price.toFixed(4).toString()
const item = this.byPrice[priceKey]
if(item) {
item.askVol += askVol
item.bidVol += bidVol
item.vol += vol
} else {
this.byPrice[priceKey] = profile
}
})
if(d.isLast()) {
const keys = Object.keys(this.byPrice)
const high = keys
.map(k => parseFloat(k, 10))
.reduce((a, b) => Math.max(a, b), 0)
const low = keys
.map(k => parseFloat(k, 10))
.reduce((a, b) => Math.min(a, b), Infinity)
const range = high - low
const greatestDelta = keys
.map(k => this.byPrice[k])
.map(({bidVol, askVol}) => Math.abs(bidVol - askVol))
.reduce((a, b) => Math.max(a, b), 0)
const numBars = range/this.contractInfo.tickSize
if(this.props.priceLevelsPerBar > 1) {
this.combinedByPrice = {}
const modifiedSize = this.contractInfo.tickSize * this.props.priceLevelsPerBar
let last, barInit
keys
.sort((a, b) => parseFloat(a, 10) - parseFloat(b, 10))
.forEach(k => {
let float = parseFloat(k, 10)
if(!barInit) barInit = float
if(last && last >= barInit + modifiedSize) {
barInit = float
}
if(last && last <= barInit + modifiedSize) {
let item = this.combinedByPrice[barInit.toFixed(4).toString()]
if(!item) (
item = { vol: 0, askVol: 0, bidVol: 0 },
item.price = barInit,
this.combinedByPrice[barInit.toFixed(4).toString()] = item
)
const byPriceItem = this.byPrice[k]
item.askVol += byPriceItem.askVol
item.bidVol += byPriceItem.bidVol
item.vol += byPriceItem.vol
}
last = float
}
)
const greatestCombinedDelta = Object.keys(this.combinedByPrice)
.map(k => this.combinedByPrice[k])
.map(({bidVol, askVol}) => Math.abs(bidVol - askVol))
.reduce((a, b) => Math.max(a, b), 0)
const combinedNumBars = range/modifiedSize
let lastPush, initPush
Object.values(this.combinedByPrice).forEach(({price, askVol, bidVol}) => {
if(!initPush) initPush = price
if(lastPush && lastPush > initPush + modifiedSize) {
initPush = price
}
if(lastPush && lastPush <= initPush + modifiedSize) {
const delta = -(bidVol - askVol)
const textLength = delta.toString().length
const barWidth = min(
px(-2),
px(-this.props.scaleFactorPx * Math.abs(delta/greatestCombinedDelta))
)
const barHeight = du(range/combinedNumBars)
const fontSize = 10
const textPt = {
x: op(
px(-(fontSize)*(.667*textLength)),
'+',
barWidth
),
y: op(du(price),'-',du(modifiedSize/2))
}
combinedBars.children.push(
{
tag: 'Shapes',
key: 'volumeBars_' + price,
primitives: [
{
tag: 'Rectangle',
position: {
x: px(0),
y: op(du(price), '-', du(modifiedSize))
},
size: {
height: barHeight,
width: barWidth
}
}
],
fillStyle: {
color:
delta > 0 ? this.props.positiveColor
: delta < 0 ? this.props.negativeColor
: '#999',
}
}
)
combinedText.children.push(
{
tag: 'Text',
key: 'text_' + price,
text: `${delta}`,
point: textPt,
style: {
fontSize,
fill: '#999'
},
textAlignment: 'rightMiddle'
}
)
}
lastPush = price
})
}
keys.map(k => this.byPrice[k])
.forEach(({price, askVol, bidVol}) => {
const delta = -(bidVol - askVol)
const textLength = delta.toString().length
const barWidth = min(
px(-2),
px(-this.props.scaleFactorPx * Math.abs(delta/greatestDelta))
)
const barHeight = op(du(range/numBars), '-', px(2))
const fontSize = 10
const textPt = {
x: op(
px(-(fontSize)*(.667*textLength)),
'+',
barWidth
),
y: du(price),
}
barsContainer.children.push(
{
tag: 'Shapes',
key: 'volumeBars_' + price,
primitives: [
{
tag: 'Rectangle',
position: {
x: px(0),
y: op(du(price), '-', du(this.contractInfo.tickSize/2))
},
size: {
height: barHeight,
width: barWidth
}
}
],
fillStyle: {
color:
delta > 0 ? this.props.positiveColor
: delta < 0 ? this.props.negativeColor
: '#999',
}
}
)
textContainer.children.push(
{
tag: 'Text',
key: 'text_' + price,
text: `${delta}`,
point: textPt,
style: {
fontSize,
fill: '#999'
},
textAlignment: 'rightMiddle'
}
)
})
}
return {
graphics: {
items: [
barsContainer,
textContainer,
combinedBars,
combinedText
]
}
}
}
}
module.exports = {
inputType: 'bars',
name: "PriceAxisDeltaCandidate",
description: "Price Axis Volume Delta",
calculator: PriceAxisDelta,
params: {
marketOpenHours: predef.paramSpecs.number(17,1,1),
marketOpenMinutes: predef.paramSpecs.number(0,1,0),
marketCloseHours: predef.paramSpecs.number(18,1,1),
marketCloseMinutes: predef.paramSpecs.number(0,1,0),
positiveColor: predef.paramSpecs.color('#9d5'),
negativeColor: predef.paramSpecs.color('#d55'),
startsYesterday: predef.paramSpecs.bool(true),
closesTomorrow: predef.paramSpecs.bool(false),
scaleFactorPx: predef.paramSpecs.number(50, 10, 10),
priceLevelsPerBar: predef.paramSpecs.number(1,1,1)
},
tags: [predef.tags.Volumes],
schemeStyles: predef.styles.solidLine("#000"),
requirements: {
volumeProfiles: true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment