Public
Edited
Mar 22, 2023
Insert cell
Insert cell
symbol = 'INJUSDT'
Insert cell
timeRange = ({
start: new Date('2023-03-13T18:50:00Z'),
end: new Date('2023-03-13T19:10:00Z'),
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// downsample = require('downsample')
downsample = require('https://bundle.run/downsample@1.4.0')
Insert cell
downsampledData = downsample.ASAP(resampledTrades.map(t => ([t.timestamp, t.normalizedPrice])), 20)
Insert cell
secondsToPlot = 600
Insert cell
Insert cell
class PriceElasticityIndicator {
updateTick(tick) {
if (!this.previousTick) {
this.previousTick = tick
return null
}

const priceChange = Math.log(tick.normalizedPrice / this.previousTick.normalizedPrice)
const volumeChange = Math.log(tick.volume / this.previousTick.volume)

if (priceChange === 0) {
if (this.previousElasticity) {
return this.previousElasticity
}
return null
}

const elasticity = volumeChange / priceChange

this.previousTick = tick
this.previousElasticity = elasticity
return elasticity
}
}

Insert cell
elasticity = {
const elasticityIndicator = new PriceElasticityIndicator();
return resampledTrades.map(t => ({
...t,
elasticity: elasticityIndicator.updateTick(t),
}))
}
Insert cell
Plot.plot({
marks: [
Plot.lineY(elasticity.slice(-secondsToPlot), {x: 'timestamp', y: 'elasticity'})
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
kineticEnergyFirHann = {
function calculateKineticEnergy(mass, velocity) {
// Calculate kinetic energy using the formula KE = 0.5 * m * v^2
const kineticEnergy = 0.5 * mass * velocity ** 2;
return kineticEnergy;
}

return acceleration.map(t => ({
...t,
kineticEnergyFirHann: calculateKineticEnergy(t.volume, t.speed)
}))
}
Insert cell
Plot.plot({
marks: [
// Plot.lineY(kineticEnergyFirHann.slice(-secondsToPlot), {x: 'timestamp', y: 'firHann', stroke: 'green'}),
Plot.lineY(kineticEnergyFirHann.slice(-secondsToPlot), {x: 'timestamp', y: 'kineticEnergyFirHann', stroke: 'blue'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.lineY(smoothedFirHann.slice(-secondsToPlot), {x: 'timestamp', y: 'smoothedFirHann', stroke: 'green'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
KalmanJS = require('https://bundle.run/kalmanjs@1.1.0')
Insert cell
smoothedFirHannKalmanJS = {
const smoother = new KalmanJS({R: 0.001, Q: 20});
const result = []

acceleration.forEach((trade, i) => {
if (i === 0 || (!trade.speed && trade.speed !== 0)) {
result.push({
...trade,
smoothedFirHann: null,
})
return
}
const smoothedFirHann = smoother.filter(trade.speed);
result.push({
...trade,
smoothedFirHann,
})
})

return result
}
Insert cell
Plot.plot({
marks: [
Plot.lineY(smoothedFirHannKalmanJS.slice(-secondsToPlot), {x: 'timestamp', y: 'smoothedFirHann', stroke: 'green'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
kineticEnergy = {
function calculateKineticEnergy(mass, velocity) {
// Calculate kinetic energy using the formula KE = 0.5 * m * v^2
const kineticEnergy = 0.5 * mass * velocity ** 2;
return kineticEnergy;
}

return filteredStates.map(t => ({
...t,
kineticEnergy: calculateKineticEnergy(t.volume, t.velocity)
}))
}
Insert cell
Plot.plot({
marks: [
Plot.lineY(kineticEnergy.slice(-secondsToPlot), {x: 'timestamp', y: 'velocity', stroke: 'green'}),
Plot.lineY(kineticEnergy.slice(-secondsToPlot), {x: 'timestamp', y: 'kineticEnergy', stroke: 'blue'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
firHannSmoothedData = {
const indicator = new FIRHann(30);
const result = []

smoothedData.forEach((x, i) => {
if (i === 0) return
const firHann = indicator.update(x, smoothedData[i-1]);
result.push(firHann)
})

return result
}
Insert cell
Plot.plot({
marks: [
Plot.lineY(firHannSmoothedData.slice(-secondsToPlot)),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
freq = {
function calculateMcLeodPitch(audioData) {
// Step 1: Compute the autocorrelation function
var corr = new Float32Array(audioData.length);
for (var i = 0; i < audioData.length; i++) {
corr[i] = 0;
for (var j = 0; j < audioData.length - i; j++) {
corr[i] += audioData[j] * audioData[j + i];
}
}
// Step 2: Compute the McLeod Pitch Estimate
var mcleod = new Float32Array(audioData.length);
var maxVal = 0;
var maxIdx = 0;
for (var i = 1; i < audioData.length; i++) {
var val =
corr[i] -
2 * corr[i - 1] +
corr[i - 2];
if (val > 0) {
mcleod[i] = Math.sqrt(val);
if (mcleod[i] > maxVal) {
maxVal = mcleod[i];
maxIdx = i;
}
} else {
mcleod[i] = 0;
}
}
// Step 3: Interpolate the peak value to improve the estimate
var y0 = mcleod[maxIdx - 1];
var y1 = mcleod[maxIdx];
var y2 = mcleod[maxIdx + 1];
var alpha = (y1 - y0) / (2 * (y1 + y2 - 2 * y0));
var pEst = maxIdx + alpha;
// Step 4: Convert the McLeod Pitch Estimate to frequency
var fEst = 1000 / pEst;
return fEst;
}
// Usage:
// Assume you have an array called `audioData` that contains the audio samples
// You can call the function to get the frequency estimate as follows:
return calculateMcLeodPitch(firHann.map(t => t.price));
}
Insert cell
class McLeodPitchMethod {
constructor(sampleRate) {
this.sampleRate = sampleRate;
this.frameSize = 100;
this.minFreq = 1; // in Hz
this.maxFreq = 10000; // in Hz
this.pitch = null;
this.frameBuffer = new Array(this.frameSize).fill(0);
}

processFrame(frame) {
// shift existing frame buffer to make room for new frame
this.frameBuffer.splice(0, frame.length);
this.frameBuffer.push(...frame);

// compute autocorrelation
const acf = new Array(this.frameSize).fill(0);
for (let i = 0; i < this.frameSize; i++) {
for (let j = 0; j < this.frameSize - i; j++) {
acf[i] += this.frameBuffer[j] * this.frameBuffer[j + i];
}
}

// apply square root function
for (let i = 0; i < this.frameSize; i++) {
acf[i] = Math.sqrt(acf[i]);
}

// find index of maximum value within frequency range
const minIdx = Math.floor(this.minFreq * this.frameSize / this.sampleRate);
const maxIdx = Math.ceil(this.maxFreq * this.frameSize / this.sampleRate);
const maxIdxInRange = acf.slice(minIdx, maxIdx + 1).reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0) + minIdx;

// convert index to frequency in microhertz
const freq = maxIdxInRange * this.sampleRate * 1e6 / this.frameSize;

this.pitch = freq;
}
}
Insert cell
mcleod = {
const indicator = new McLeodPitchMethod(1);
return indicator.processFrame(firHann.slice(-300));
// const result = []

// firHann.forEach((trade, i) => {
// if (i === 0) return
// const mcleod = indicator.processFrame([trade.firHann]);
// result.push({
// timestamp: trade.timestamp,
// mcleod,
// price: trade.price,
// })
// })

// return result
}
Insert cell
// pitchSimple = {
// function pitchDetection(sampleRate, signal) {
// // Compute the autocorrelation of the signal
// let r = autocorrelation(signal);
// // Find the peak of the autocorrelation function
// let peakIndex = findPeak(r);
// // Compute the pitch period in samples
// let period = peakIndex;
// // Compute the pitch frequency in Hz
// let frequency = sampleRate / period;
// return frequency;
// }
// function autocorrelation(signal) {
// let len = signal.length;
// let r = new Float32Array(len);
// let mean = 0;
// for (let i = 0; i < len; i++) {
// mean += signal[i];
// }
// mean /= len;
// for (let lag = 0; lag < len; lag++) {
// let sum = 0;
// for (let i = 0; i < len - lag; i++) {
// sum += (signal[i] - mean) * (signal[i + lag] - mean);
// }
// r[lag] = sum / (len - lag);
// }
// return r;
// }
// function findPeak(r) {
// let len = r.length;
// let maxVal = -1;
// let maxIndex = -1;
// for (let i = 0; i < len; i++) {
// if (r[i] > maxVal) {
// maxVal = r[i];
// maxIndex = i;
// }
// }
// return maxIndex;
// }

// return pitchDetection(44100, filteredStates.map(t => t.velocity))
// }
Insert cell
// pitchCycle = {
// function mcleodPitchMethod(data) {
// // Compute autocorrelation
// const r = new Array(data.length).fill(0);
// for (let tau = 0; tau < data.length; tau++) {
// let sum = 0;
// for (let i = 0; i < data.length - tau; i++) {
// sum += data[i] * data[i + tau];
// }
// r[tau] = sum;
// }
// // Find maximum peak in autocorrelation function
// let maxVal = -Infinity;
// let maxIndex = -1;
// for (let i = 1; i < r.length; i++) {
// if (r[i] > maxVal) {
// maxVal = r[i];
// maxIndex = i;
// }
// }
// // Compute frequency from index of maximum peak
// const frequency = maxIndex / data.length;
// // Convert frequency to Microhertz
// const frequencyMicroHz = frequency * 1000000;
// // Calculate sign of first difference
// let sign = null;
// if (data.length > 1) {
// sign = Math.sign(data[1] - data[0]);
// }
// return {
// frequencyMicroHz,
// sign,
// };
// }

// return mcleodPitchMethod(firHann.map(t => t.firHann));
// }
Insert cell
class PitchIndicator {
constructor(sampleRate) {
this.sampleRate = sampleRate;
this.pitch = -1;
this.accuracy = 0;

this.bufferSize = sampleRate * 0.1; // Use a buffer size of 100 ms
this.inputBuffer = new Float32Array(this.bufferSize);
this.corrBuffer = new Float32Array(this.bufferSize);
this.cepsBuffer = new Float32Array(this.bufferSize);
this.cepsLifter = new Float32Array(this.bufferSize);
this.cepsPeak = 0;
this.cepsMaxVal = -Infinity;
this.cepsReady = false;
}

process(sample) {
// Add new sample to the input buffer
this.inputBuffer.set(this.inputBuffer.subarray(1));
this.inputBuffer[this.inputBuffer.length - 1] = sample;

// Calculate autocorrelation
this.corrBuffer.fill(0);
for (let i = 0; i < this.bufferSize; i++) {
for (let j = 0; j < this.bufferSize - i; j++) {
this.corrBuffer[i] += this.inputBuffer[j] * this.inputBuffer[j + i];
}
}

// Calculate cepstrum
this.cepsBuffer.fill(0);
for (let i = 0; i < this.bufferSize; i++) {
for (let j = 0; j < i; j++) {
this.cepsBuffer[i] += this.corrBuffer[j] * this.cepsBuffer[i - j];
}
this.cepsBuffer[i] = Math.log(Math.abs(this.cepsBuffer[i] + 1e-10));
}

// Apply liftering
this.cepsLifter.fill(0);
let lifterSize = Math.round(this.sampleRate / 2000); // Use a lifter size of 5 ms
for (let i = 0; i < lifterSize; i++) {
this.cepsLifter[i] = 1;
}
for (let i = this.bufferSize - lifterSize; i < this.bufferSize; i++) {
this.cepsLifter[i] = 1;
}
for (let i = 0; i < this.bufferSize; i++) {
this.cepsBuffer[i] *= this.cepsLifter[i];
}

// Find pitch estimate
if (this.cepsReady) {
for (let i = 0; i < this.bufferSize / 2; i++) {
if (this.cepsBuffer[i] > this.cepsMaxVal) {
this.cepsPeak = i;
this.cepsMaxVal = this.cepsBuffer[i];
}
}
this.pitch = this.sampleRate / this.cepsPeak;
this.accuracy = this.cepsMaxVal;
}
this.cepsReady = true;

return {
pitch: this.pitch,
accuracy: this.accuracy,
};
}
}
Insert cell
// pitch = {
// let pitchIndicator = new PitchIndicator(44100);
// const result = []

// filteredStates.slice(100).forEach((trade, i) => {
// if (i === 0) return
// let pitch = pitchIndicator.process(trade.velociity);
// result.push({
// timestamp: trade.timestamp,
// pitch: pitch.pitch,
// pitchAccuracy: pitch.accuracy,
// price: trade.price,
// })
// })

// return result
// }
Insert cell
// Plot.plot({
// marks: [
// Plot.lineY(pitch, {x: 'timestamp', y: 'pitch', stroke: 'blue'}),
// // Plot.lineY(filteredStates.slice(-300), {x: 'timestamp', y: 'acceleration', stroke: 'red'}),
// ],
// grid: true,
// style: {
// width: 1024,
// }
// })
Insert cell
Plot.plot({
marks: [
Plot.lineY(pitch.slice(-300), {x: 'timestamp', y: 'pitchAccuracy', stroke: 'blue'}),
// Plot.lineY(filteredStates.slice(-300), {x: 'timestamp', y: 'acceleration', stroke: 'red'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more