Public
Edited
Mar 29, 2023
2 forks
Insert cell
Insert cell
symbol = 'PERLUSDT'
Insert cell
timeframe = '1s'
Insert cell
endpoint = `https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${timeframe}&limit=1000`
Insert cell
candles = (await fetch(endpoint)).json()
Insert cell
normalizedCandles = candles.map(c => ({timestamp: c[0], close: Number(c[4]), volume: Number(c[5])}))
Insert cell
Plot.plot({
marks: [
// Plot.ruleY([0]),
Plot.lineY(normalizedCandles, {x: 'timestamp', y: 'close'})
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
class MatrixUtils {
// Compute the transpose of a matrix.
static transpose(matrix) {
const m = matrix.length
const n = matrix[0].length
const result = new Array(n)
for (let j = 0; j < n; j++) {
result[j] = new Array(m)
for (let i = 0; i < m; i++) {
result[j][i] = matrix[i][j]
}
}
return result
}

// Compute the product of two matrices.
static multiply(matrix1, matrix2) {
const m = matrix1.length
const n = matrix2[0].length
const result = new Array(m)
for (let i = 0; i < m; i++) {
result[i] = new Array(n)
for (let j = 0; j < n; j++) {
let sum = 0
for (let k = 0; k < matrix1[0].length; k++) {
sum += matrix1[i][k] * matrix2[k][j]
}
result[i][j] = sum
}
}
return result
}

// Compute the sum of two matrices.
static sum(matrix1, matrix2) {
const m = matrix1.length
const n = matrix1[0].length
const result = new Array(m)
for (let i = 0; i < m; i++) {
result[i] = new Array(n)
for (let j = 0; j < n; j++) {
result[i][j] = matrix1[i][j] + matrix2[i][j]
}
}
return result
}

// Compute the difference of two matrices.
static subtract(matrix1, matrix2) {
const m = matrix1.length
const n = matrix1[0].length
const result = new Array(m)
for (let i = 0; i < m; i++) {
result[i] = new Array(n)
for (let j = 0; j < n; j++) {
result[i][j] = matrix1[i][j] - matrix2[i][j]
}
}
return result
}
}
Insert cell
// Define the Extended Kalman Filter
class ExtendedKalmanFilter {
constructor(state, covariance) {
this.state = state
this.covariance = covariance
}

// Predict the state and covariance given the elapsed time since the last prediction
predict(dt) {
// Calculate the state transition matrix and Jacobian
const stateTransitionMatrix = this.jacobianF(this.state, dt)
const jacobianMatrix = this.jacobianF(this.state, dt)

// Predict the state using the state transition function
this.state = this.stateTransition(this.state, dt)

// Predict the covariance using the state transition matrix and Jacobian
const covariance = this.covariance
this.covariance = MatrixUtils.sum(
MatrixUtils.multiply(
stateTransitionMatrix,
MatrixUtils.multiply(
covariance,
MatrixUtils.transpose(stateTransitionMatrix)
)
),
MatrixUtils.multiply(
jacobianMatrix,
MatrixUtils.multiply(covariance, MatrixUtils.transpose(jacobianMatrix))
)
)

// Add process noise to the covariance
const q = [
[10, 0, 0],
[0, 10, 0],
[0, 0, 10]
]
this.covariance = MatrixUtils.sum(this.covariance, q)
}

// Update the state and covariance given a new observation
update(observation, measurementNoise) {
// Calculate the observation matrix and Jacobian
const observationMatrix = this.jacobianH(this.state)

// Calculate the innovation and innovation covariance
const innovation = observation - this.state.price
const innovationCovariance = MatrixUtils.sum(
MatrixUtils.multiply(
observationMatrix,
MatrixUtils.multiply(
this.covariance,
MatrixUtils.transpose(observationMatrix)
)
),
measurementNoise
)

// Calculate the Kalman gain
const kalmanGain = MatrixUtils.multiply(
this.covariance,
MatrixUtils.transpose(observationMatrix)
).map(row => row.map(value => value / innovationCovariance[0][0]))

// Update the state and covariance using the Kalman gain and innovation
const deltaState = MatrixUtils.multiply(kalmanGain, [[innovation]])
this.state.price += deltaState[0][0]
this.state.velocity += deltaState[1][0]
this.state.acceleration += deltaState[2][0]
this.covariance = MatrixUtils.subtract(
this.covariance,
MatrixUtils.multiply(
kalmanGain,
MatrixUtils.multiply(observationMatrix, this.covariance)
)
)
}

// Define the state transition function f(x)
stateTransition(state, dt) {
const price =
state.price + state.velocity * dt + 0.5 * state.acceleration * dt * dt
const velocity = state.velocity + state.acceleration * dt
const acceleration = state.acceleration

return { price, velocity, acceleration }
}

// Define the Jacobian of the state transition function f(x)
jacobianF(state, dt) {
return [
[1, dt, 0.5 * dt * dt],
[0, 1, dt],
[0, 0, 1]
]
}

// Define the Jacobian of the observation function h(x)
jacobianH(state) {
return [[1, 0, 0]]
}
}
Insert cell
filteredStates = {
const dt = 1
const state2 = { price: normalizedCandles[0].close, 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 = []

normalizedCandles.forEach((observation, i) => {
filter.update(observation.close, [[10]])
const state = {
timestamp: observation.timestamp,
...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.close
})
})

return states
}

Insert cell
Plot.plot({
marks: [
Plot.lineY(filteredStates, {x: 'timestamp', y: 'truth'}),
Plot.lineY(filteredStates, {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
Plot.plot({
marks: [
// Plot.ruleY([0]),
// Plot.dot(filteredStates.slice(500).slice(-100), {x: 'timestamp', y: 'predictedVelocity', stroke: 'green'}),
// Plot.lineY(filteredStates.slice(500).slice(-100), {x: 'timestamp', y: 'predictedVelocity', stroke: 'lightgreen'}),
Plot.lineY(filteredStates, {x: 'timestamp', y: 'velocity', stroke: 'green'}),
// Plot.dot(filteredStates.slice(500).slice(-100), {x: 'timestamp', y: 'predictedAcceleration', stroke: 'red'}),
// Plot.lineY(filteredStates.slice(500).slice(-100), {x: 'timestamp', y: 'predictedAcceleration', stroke: 'pink'}),
Plot.lineY(filteredStates, {x: 'timestamp', y: 'acceleration', stroke: 'red'}),
],
grid: true,
style: {
width: 1024,
}
})
Insert cell
normalizedPrices = {
const result = []
const initialPrice = 1
result.push(price)
}
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