Datafeed limping
This commit is contained in:
@@ -6,6 +6,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico"/>
|
<link rel="icon" href="/favicon.ico"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>dexorder</title>
|
<title>dexorder</title>
|
||||||
|
<!-- The script for CryptoCompare WebSocket -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
subscribeOnStream,
|
||||||
|
unsubscribeFromStream,
|
||||||
|
} from './streaming.js';
|
||||||
|
|
||||||
import {jBars} from './jBars.js';
|
import {jBars} from './jBars.js';
|
||||||
import {metadata} from "@/version.js";
|
import {metadata} from "@/version.js";
|
||||||
import FlexSearch from "flexsearch";
|
import FlexSearch from "flexsearch";
|
||||||
@@ -121,24 +126,6 @@ export default {
|
|||||||
setTimeout(() => callback(configurationData));
|
setTimeout(() => callback(configurationData));
|
||||||
},
|
},
|
||||||
|
|
||||||
// searchSymbols: async (
|
|
||||||
// userInput,
|
|
||||||
// exchange,
|
|
||||||
// symbolType,
|
|
||||||
// onResultReadyCallback,
|
|
||||||
// ) => {
|
|
||||||
// console.log('[searchSymbols]: Method call');
|
|
||||||
// const symbols = await getAllSymbols();
|
|
||||||
// const newSymbols = symbols.filter(symbol => {
|
|
||||||
// const isExchangeValid = exchange === '' || symbol.exchange === exchange;
|
|
||||||
// const isFullSymbolContainsInput = symbol.full_name
|
|
||||||
// .toLowerCase()
|
|
||||||
// .indexOf(userInput.toLowerCase()) !== -1;
|
|
||||||
// return isExchangeValid && isFullSymbolContainsInput;
|
|
||||||
// });
|
|
||||||
// onResultReadyCallback(newSymbols);
|
|
||||||
// },
|
|
||||||
|
|
||||||
searchSymbols: async (
|
searchSymbols: async (
|
||||||
userInput,
|
userInput,
|
||||||
exchange,
|
exchange,
|
||||||
@@ -207,6 +194,7 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(`[getBars]: returned ${bars.length} bar(s)`);
|
console.log(`[getBars]: returned ${bars.length} bar(s)`);
|
||||||
|
console.log(bars);
|
||||||
onHistoryCallback(bars, {
|
onHistoryCallback(bars, {
|
||||||
noData: false,
|
noData: false,
|
||||||
});
|
});
|
||||||
@@ -224,9 +212,20 @@ export default {
|
|||||||
onResetCacheNeededCallback,
|
onResetCacheNeededCallback,
|
||||||
) => {
|
) => {
|
||||||
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
|
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
|
||||||
|
return; // disable
|
||||||
|
subscribeOnStream(
|
||||||
|
symbolInfo,
|
||||||
|
resolution,
|
||||||
|
onRealtimeCallback,
|
||||||
|
subscriberUID,
|
||||||
|
onResetCacheNeededCallback,
|
||||||
|
lastBarsCache.get(symbolInfo.full_name),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
unsubscribeBars: (subscriberUID) => {
|
unsubscribeBars: (subscriberUID) => {
|
||||||
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
|
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
|
||||||
|
return; // disable
|
||||||
|
unsubscribeFromStream(subscriberUID);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
32
src/charts/helpers.js
Normal file
32
src/charts/helpers.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Makes requests to CryptoCompare API
|
||||||
|
export async function makeApiRequest(path) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://min-api.cryptocompare.com/${path}`);
|
||||||
|
return response.json();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`CryptoCompare request error: ${error.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a symbol ID from a pair of the coins
|
||||||
|
export function generateSymbol(exchange, fromSymbol, toSymbol) {
|
||||||
|
const short = `${fromSymbol}/${toSymbol}`;
|
||||||
|
return {
|
||||||
|
short,
|
||||||
|
full: `${exchange}:${short}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all parts of the symbol
|
||||||
|
export function parseFullSymbol(fullSymbol) {
|
||||||
|
const match = fullSymbol.match(/^(\w+):(\w+)\/(\w+)$/);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exchange: match[1],
|
||||||
|
fromSymbol: match[2],
|
||||||
|
toSymbol: match[3],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,29 +2,36 @@ export async function jBars (from, to, res) {
|
|||||||
|
|
||||||
console.log('[jBars]: Method call', res, from, to);
|
console.log('[jBars]: Method call', res, from, to);
|
||||||
|
|
||||||
var fromDate = new Date(from*1000);
|
|
||||||
var toDate = new Date(to*1000);
|
var toDate = new Date(to*1000);
|
||||||
|
|
||||||
|
var fromDate = new Date(from*1000);
|
||||||
|
if (res=="1W") { // for 1W, day must be Sunday
|
||||||
|
const day = fromDate.getUTCDay(); // 0<=day<7
|
||||||
|
fromDate.setDate(fromDate.getDate() + (7-day)%7 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set fromDate to be compatible with Tim's datafiles.
|
||||||
|
// This potentially increases number of samples returned.
|
||||||
|
|
||||||
|
if (res.endsWith("W") || res.endsWith("D")) { // Days/Weeks -- set to midnight
|
||||||
|
fromDate.setUTCHours(0, 0, 0);
|
||||||
|
} else {
|
||||||
|
let minutesRes = parseInt(res);
|
||||||
|
if (minutesRes >= 60) { // Hours
|
||||||
|
let hoursRes = Math.floor(minutesRes/60);
|
||||||
|
let fromHours = fromDate.getUTCHours();
|
||||||
|
fromDate.setUTCHours(fromHours - fromHours % hoursRes, 0, 0, 0);
|
||||||
|
} else { // Minutes
|
||||||
|
let fromMinutes = fromDate.getUTCMinutes();
|
||||||
|
fromDate.setUTCMinutes(fromMinutes - fromMinutes % minutesRes, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log("fromDate:", fromDate.toUTCString());
|
console.log("fromDate:", fromDate.toUTCString());
|
||||||
console.log("toDate: ", toDate.toUTCString());
|
console.log("toDate: ", toDate.toUTCString());
|
||||||
|
|
||||||
const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443";
|
const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443";
|
||||||
|
// const contract = "0xC6962004f452bE9203591991D15f6b388e09E8D0";
|
||||||
// check parameters
|
|
||||||
// console.assert(fromDate.getUTCHours() == 0, "hours should be zero");
|
|
||||||
// console.assert(fromDate.getUTCMinutes() == 0, "minutes should be zero");
|
|
||||||
// console.assert(fromDate.getUTCSeconds() == 0, "seconds should be zero");
|
|
||||||
// console.assert(fromDate.getUTCMilliseconds() == 0, "milliseconds should be zero");
|
|
||||||
|
|
||||||
// Spoof data
|
|
||||||
|
|
||||||
// var spoof;
|
|
||||||
// {
|
|
||||||
// const yr = "2022";
|
|
||||||
// const mo = "01";
|
|
||||||
// const url = `/ohlc/42161/${contract}/${res}/${yr}/${contract}-${res}-${yr}${mo}.json`
|
|
||||||
// const response = await fetch(url);
|
|
||||||
// spoof = await response.json();
|
|
||||||
// }
|
|
||||||
|
|
||||||
const file_res = ['1m', '3m', '5m', '10m', '15m', '30m', '1H', '2H', '4H', '8H', '12H', '1D', '2D', '3D', '1W',];
|
const file_res = ['1m', '3m', '5m', '10m', '15m', '30m', '1H', '2H', '4H', '8H', '12H', '1D', '2D', '3D', '1W',];
|
||||||
const supported_res = ['1', '3', '5', '10', '15', '30', '60', '120', '240', '480', '720', '1D', '2D', '3D', '1W',];
|
const supported_res = ['1', '3', '5', '10', '15', '30', '60', '120', '240', '480', '720', '1D', '2D', '3D', '1W',];
|
||||||
@@ -64,13 +71,18 @@ export async function jBars (from, to, res) {
|
|||||||
const mo = String(iDate.getUTCMonth()+1).padStart(2, '0'); // January is month 0 in Date object
|
const mo = String(iDate.getUTCMonth()+1).padStart(2, '0'); // January is month 0 in Date object
|
||||||
const date = is_daily_res ? String(iDate.getUTCDate()).padStart(2, '0') : "";
|
const date = is_daily_res ? String(iDate.getUTCDate()).padStart(2, '0') : "";
|
||||||
const yrmo = !is_single_res ? `-${yr}${mo}` : "";
|
const yrmo = !is_single_res ? `-${yr}${mo}` : "";
|
||||||
|
const server = "https://alpha.dexorder.trade"
|
||||||
|
|
||||||
let url = `/ohlc/42161/${contract}/${fres}${yrdir}/${contract}-${fres}${yrmo}${date}.json`;
|
let url = `${server}/ohlc/42161/${contract}/${fres}${yrdir}/${contract}-${fres}${yrmo}${date}.json`;
|
||||||
|
|
||||||
let response = await fetch(url);
|
let response = await fetch(url);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
ohlc = await response.json();
|
ohlc = await response.json();
|
||||||
|
console.log(`Fetch: ${ohlc.length} samples from ${url}`)
|
||||||
|
console.log(`from: ${new Date(ohlc[0][0]*1000)}`)
|
||||||
|
console.log(`to: ${new Date(ohlc[ohlc.length-1][0]*1000)}`)
|
||||||
} else {
|
} else {
|
||||||
|
console.log(`Fetch: file not found: ${url}`)
|
||||||
ohlc = []; // no file, then empty data
|
ohlc = []; // no file, then empty data
|
||||||
}
|
}
|
||||||
iFile = new Date(iDate);
|
iFile = new Date(iDate);
|
||||||
@@ -80,14 +92,18 @@ export async function jBars (from, to, res) {
|
|||||||
// Skip samples not for our time
|
// Skip samples not for our time
|
||||||
|
|
||||||
for(; iohlc < ohlc.length; iohlc++ ) {
|
for(; iohlc < ohlc.length; iohlc++ ) {
|
||||||
if ( new Date(ohlc[iohlc][0]+'Z').getTime() >= iDate.getTime() ) break;
|
// if ( new Date(ohlc[iohlc][0]+'Z').getTime() >= iDate.getTime() ) break;
|
||||||
|
if ( ohlc[iohlc][0]*1000 >= iDate.getTime() ) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ohlcDate = iohlc >= ohlc.length ? undefined : new Date(ohlc[iohlc][0]+'Z');
|
// console.log(`iohlc: ${iohlc}`);
|
||||||
|
|
||||||
|
// let ohlcDate = iohlc >= ohlc.length ? undefined : new Date(ohlc[iohlc][0]+'Z');
|
||||||
|
let ohlcDate = iohlc >= ohlc.length ? undefined : new Date(ohlc[iohlc][0]*1000);
|
||||||
|
|
||||||
// no ohlc sample file, insert missing sample
|
// no ohlc sample file, insert missing sample
|
||||||
|
|
||||||
const visible_missing_samples = true;
|
const visible_missing_samples = false;
|
||||||
if (ohlcDate == undefined) {
|
if (ohlcDate == undefined) {
|
||||||
bar = {
|
bar = {
|
||||||
time: iDate.getTime(),
|
time: iDate.getTime(),
|
||||||
@@ -102,9 +118,7 @@ export async function jBars (from, to, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// file exists, but ohlc sample not for this time, insert missing sample
|
// file exists, but ohlc sample not for this time, insert missing sample
|
||||||
|
|
||||||
else if ( iDate.getTime() != ohlcDate.getTime() ) {
|
else if ( iDate.getTime() != ohlcDate.getTime() ) {
|
||||||
|
|
||||||
bar = {
|
bar = {
|
||||||
time: iDate.getTime(),
|
time: iDate.getTime(),
|
||||||
}
|
}
|
||||||
@@ -114,10 +128,10 @@ export async function jBars (from, to, res) {
|
|||||||
low: 0,
|
low: 0,
|
||||||
close: 0,
|
close: 0,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Copy ohlc sample
|
// Copy ohlc sample
|
||||||
|
else {
|
||||||
} else {
|
|
||||||
bar = {
|
bar = {
|
||||||
time: iDate.getTime(),
|
time: iDate.getTime(),
|
||||||
open: ohlc[iohlc][1] ?? ohlc[iohlc][4], // open
|
open: ohlc[iohlc][1] ?? ohlc[iohlc][4], // open
|
||||||
|
|||||||
171
src/charts/streaming.js
Normal file
171
src/charts/streaming.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { parseFullSymbol } from './helpers.js';
|
||||||
|
|
||||||
|
const socket = io('wss://streamer.cryptocompare.com');
|
||||||
|
const channelToSubscription = new Map();
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log('[socket] Connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', (reason) => {
|
||||||
|
console.log('[socket] Disconnected:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
console.log('[socket] Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('m', data => {
|
||||||
|
console.log('[socket] Message:', data);
|
||||||
|
const [
|
||||||
|
eventTypeStr,
|
||||||
|
exchange,
|
||||||
|
fromSymbol,
|
||||||
|
toSymbol,
|
||||||
|
,
|
||||||
|
,
|
||||||
|
tradeTimeStr,
|
||||||
|
,
|
||||||
|
tradePriceStr,
|
||||||
|
] = data.split('~');
|
||||||
|
|
||||||
|
if (parseInt(eventTypeStr) !== 0) {
|
||||||
|
// Skip all non-trading events
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tradePrice = parseFloat(tradePriceStr);
|
||||||
|
const tradeTime = parseInt(tradeTimeStr);
|
||||||
|
const channelString = `0~${exchange}~${fromSymbol}~${toSymbol}`;
|
||||||
|
const subscriptionItem = channelToSubscription.get(channelString);
|
||||||
|
if (subscriptionItem === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastDailyBar = subscriptionItem.lastDailyBar;
|
||||||
|
const nextDailyBarTime = getNextDailyBarTime(lastDailyBar.time, subscriptionItem.resolution);
|
||||||
|
|
||||||
|
console.log("tradeTime ", tradeTime, new Date(tradeTime))
|
||||||
|
console.log("lastDailyBar.time", lastDailyBar.time, new Date(lastDailyBar.time))
|
||||||
|
console.log("nextDailyBarTime ", nextDailyBarTime, new Date(nextDailyBarTime))
|
||||||
|
|
||||||
|
let bar;
|
||||||
|
if (tradeTime >= nextDailyBarTime) {
|
||||||
|
bar = {
|
||||||
|
time: nextDailyBarTime,
|
||||||
|
open: tradePrice,
|
||||||
|
high: tradePrice,
|
||||||
|
low: tradePrice,
|
||||||
|
close: tradePrice,
|
||||||
|
};
|
||||||
|
console.log('[socket] Generate new bar', bar);
|
||||||
|
console.log("time:", bar.time.toString(), new Date(bar.time).toUTCString())
|
||||||
|
} else {
|
||||||
|
bar = {
|
||||||
|
...lastDailyBar,
|
||||||
|
high: Math.max(lastDailyBar.high, tradePrice),
|
||||||
|
low: Math.min(lastDailyBar.low, tradePrice),
|
||||||
|
close: tradePrice,
|
||||||
|
};
|
||||||
|
console.log('[socket] Update the latest bar by price', tradePrice);
|
||||||
|
}
|
||||||
|
subscriptionItem.lastDailyBar = bar;
|
||||||
|
|
||||||
|
// Send data to every subscriber of that symbol
|
||||||
|
subscriptionItem.handlers.forEach(handler => handler.callback(bar));
|
||||||
|
});
|
||||||
|
|
||||||
|
function getNextDailyBarTime(barTime, res) {
|
||||||
|
const date = new Date(barTime);
|
||||||
|
const resDigits = res.slice(0, -1)
|
||||||
|
if (res.endsWith("W")) {
|
||||||
|
date.setDate(date.getDate() + parseInt(resDigits)*7);
|
||||||
|
} else if (res.endsWith("D")) {
|
||||||
|
date.setDate(date.getDate() + parseInt(resDigits));
|
||||||
|
} else {
|
||||||
|
date.setMinutes(date.getMinutes() + parseInt(res))
|
||||||
|
}
|
||||||
|
return date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribeOnStream(
|
||||||
|
symbolInfo,
|
||||||
|
resolution,
|
||||||
|
onRealtimeCallback,
|
||||||
|
subscriberUID,
|
||||||
|
onResetCacheNeededCallback,
|
||||||
|
lastDailyBar,
|
||||||
|
) {
|
||||||
|
// return;
|
||||||
|
const parsedSymbol = parseFullSymbol(symbolInfo.full_name);
|
||||||
|
const channelString = `0~${parsedSymbol.exchange}~${parsedSymbol.fromSymbol}~${parsedSymbol.toSymbol}`;
|
||||||
|
const handler = {
|
||||||
|
id: subscriberUID,
|
||||||
|
callback: onRealtimeCallback,
|
||||||
|
};
|
||||||
|
let subscriptionItem = channelToSubscription.get(channelString);
|
||||||
|
if (subscriptionItem) {
|
||||||
|
// Already subscribed to the channel, use the existing subscription
|
||||||
|
subscriptionItem.handlers.push(handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscriptionItem = {
|
||||||
|
subscriberUID,
|
||||||
|
resolution,
|
||||||
|
lastDailyBar,
|
||||||
|
handlers: [handler],
|
||||||
|
};
|
||||||
|
channelToSubscription.set(channelString, subscriptionItem);
|
||||||
|
console.log('[subscribeBars]: Subscribe to streaming. Channel:', channelString);
|
||||||
|
socket.emit('SubAdd', { subs: [channelString] });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsubscribeFromStream(subscriberUID) {
|
||||||
|
// return;
|
||||||
|
// Find a subscription with id === subscriberUID
|
||||||
|
for (const channelString of channelToSubscription.keys()) {
|
||||||
|
const subscriptionItem = channelToSubscription.get(channelString);
|
||||||
|
const handlerIndex = subscriptionItem.handlers
|
||||||
|
.findIndex(handler => handler.id === subscriberUID);
|
||||||
|
|
||||||
|
if (handlerIndex !== -1) {
|
||||||
|
// Remove from handlers
|
||||||
|
subscriptionItem.handlers.splice(handlerIndex, 1);
|
||||||
|
|
||||||
|
if (subscriptionItem.handlers.length === 0) {
|
||||||
|
// Unsubscribe from the channel if it was the last handler
|
||||||
|
console.log('[unsubscribeBars]: Unsubscribe from streaming. Channel:', channelString);
|
||||||
|
socket.emit('SubRemove', { subs: [channelString] });
|
||||||
|
channelToSubscription.delete(channelString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sim() {
|
||||||
|
// Assuming these variables hold the data you extracted earlier
|
||||||
|
const eventTypeStr = "0";
|
||||||
|
const exchange = "Uniswap";
|
||||||
|
const fromSymbol = "WETH";
|
||||||
|
const toSymbol = "USD";
|
||||||
|
const tradeTimeStr = (Date.now()).toString();
|
||||||
|
const tradePriceStr = (55+Date.now()%23).toString();
|
||||||
|
|
||||||
|
// Constructing the original string
|
||||||
|
const data = [
|
||||||
|
eventTypeStr,
|
||||||
|
exchange,
|
||||||
|
fromSymbol,
|
||||||
|
toSymbol,
|
||||||
|
'', // Placeholder for the fifth element
|
||||||
|
'', // Placeholder for the sixth element
|
||||||
|
tradeTimeStr,
|
||||||
|
'', // Placeholder for the eighth element
|
||||||
|
tradePriceStr,
|
||||||
|
].join('~');
|
||||||
|
socket._callbacks['$m'][0](data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// window.sim = sim;
|
||||||
|
socket._callbacks['$connect'][0]();
|
||||||
|
setInterval(sim, 10*1000);
|
||||||
|
;
|
||||||
Reference in New Issue
Block a user