Public
Edited
Mar 12, 2023
1 fork
Insert cell
Insert cell
symbol = 'FISUSDT'
Insert cell
timeRange = ({
start: new Date('2023-03-12T14:53:00Z'),
end: new Date('2023-03-12T15:13:00Z'),
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
secondsToPlot = 1100 // Five minutes
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
Insert cell
filtedTrades = {
// Apply kalman filter
const kalmanFilterPrice = new KalmanJS({R: 0.01, Q: 1/60, A: 1.1});
const kalmanFilterLogReturns = new KalmanJS({R: 0.01, Q: 20, A: 1.1});
return resampledTrades.map(t => ({
...t,
filteredPrice: kalmanFilterPrice.filter(t.normalizedPrice),
filteredLogReturn: kalmanFilterLogReturns.filter(t.logReturn),
}))
}
Insert cell
Plot.plot({
marks: [
// Plot.lineY(filtedTrades.slice(-secondsToPlot), {x: 'timestamp', y: 'normalizedPrice'}),
Plot.lineY(filtedTrades.slice(-secondsToPlot), {x: 'timestamp', y: 'filteredPrice', stroke: 'blue'})
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Plot.plot({
marks: [
Plot.lineY(filtedTrades.slice(-secondsToPlot), {x: 'timestamp', y: 'logReturn'}),
Plot.lineY(filtedTrades.slice(-secondsToPlot), {x: 'timestamp', y: 'filteredLogReturn', stroke: 'blue'})
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
firRLS = {
const indicator = new FIRRLS(12, 0.0001);
const result = []

resampledTrades.forEach((trade, i) => {
if (i === 0) return
const rls = indicator.update(trade.normalizedPrice, resampledTrades[i-1].normalizedPrice);
result.push({
timestamp: trade.timestamp,
rls,
price: trade.price,
})
})

return result
}
Insert cell
Plot.plot({
marks: [
Plot.lineY(firRLS.slice(-secondsToPlot), {x: 'timestamp', y: 'rls'})
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
Insert cell
class KalmanSmoother {
constructor() {
this.A = 1; // State transition matrix
this.H = 1; // Observation matrix
this.Q = 0.01; // Process noise covariance
this.R = 1; // Observation noise covariance
this.P = 1; // Estimate error covariance

this.x = 0; // Initial state estimate
this.K = 0; // Kalman Gain
}

smooth(data) {
let smoothedData = [];
let n = data.length;

for (let i = 0; i < n; i++) {
// Predicted state estimate
let x_hat = this.A * this.x;
// Predicted error covariance
let P_hat = this.A * this.P * this.A + this.Q;
// Kalman Gain
this.K = P_hat * this.H / (P_hat * this.H * this.H + this.R);
// Updated state estimate
this.x = x_hat + this.K * (data[i] - this.H * x_hat);
// Updated error covariance
this.P = (1 - this.K * this.H) * P_hat;
smoothedData.push(this.x);
}

return smoothedData;
}
}
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.lineY(smoothedData.slice(-secondsToPlot)),
// Plot.lineY(filteredStates.slice(-180), {x: 'timestamp', y: 'predictedPrice', stroke: 'grey'}),
// Plot.lineY(filteredStates.slice(-100), {x: 'timestamp', y: 'price', stroke: 'lightgrey'}),
// Plot.dot(filteredStates.slice(499).slice(-300), {x: 'timestamp', y: 'predictedPrice', stroke: 'grey'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
Insert cell
Insert cell
filteredStates = {
const data = resampledTrades.map((t, i) => ({...t, smoothed: smoothedData[i]}))
const dt = 1/60
const state2 = { price: data[0].smoothed, velocity: 0.0, acceleration: 0.0 }
const covariance = [
[dt, 0.0, 0.0],
[0.0, dt, 0.0],
[0.0, 0.0, dt]
]
const filter = new ExtendedKalmanFilter(state2, covariance)
const states = []
const predictedStates = []

data.forEach((observation, i) => {
filter.update(observation.smoothed, [[1/60]])
const state = {
...observation,
...filter.state
}
filter.predict(dt)
const predictedState = {
predictedPrice: filter.state.price,
predictedVelocity: filter.state.velocity,
predictedAcceleration: filter.state.acceleration,
}
predictedStates.push(predictedState)
states.push({
...state,
...(predictedStates[i-1] ? predictedStates[i-1] : predictedState),
truth: observation.price
})
})

return states
}
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
firHannSmoothed = {
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(firHannSmoothed.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