diff --git a/index.html b/index.html
index 3b524b8..06ea651 100644
--- a/index.html
+++ b/index.html
@@ -6,6 +6,8 @@
dexorder
+
+
diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js
index 67ddf77..06168e7 100644
--- a/src/charts/datafeed.js
+++ b/src/charts/datafeed.js
@@ -1,3 +1,8 @@
+import {
+ subscribeOnStream,
+ unsubscribeFromStream,
+} from './streaming.js';
+
import {jBars} from './jBars.js';
import {metadata} from "@/version.js";
import FlexSearch from "flexsearch";
@@ -121,24 +126,6 @@ export default {
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 (
userInput,
exchange,
@@ -207,6 +194,7 @@ export default {
});
}
console.log(`[getBars]: returned ${bars.length} bar(s)`);
+ console.log(bars);
onHistoryCallback(bars, {
noData: false,
});
@@ -224,9 +212,20 @@ export default {
onResetCacheNeededCallback,
) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
+ return; // disable
+ subscribeOnStream(
+ symbolInfo,
+ resolution,
+ onRealtimeCallback,
+ subscriberUID,
+ onResetCacheNeededCallback,
+ lastBarsCache.get(symbolInfo.full_name),
+ );
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
+ return; // disable
+ unsubscribeFromStream(subscriberUID);
},
};
diff --git a/src/charts/helpers.js b/src/charts/helpers.js
new file mode 100644
index 0000000..9e32849
--- /dev/null
+++ b/src/charts/helpers.js
@@ -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],
+ };
+}
diff --git a/src/charts/jBars.js b/src/charts/jBars.js
index 28f901d..669f892 100644
--- a/src/charts/jBars.js
+++ b/src/charts/jBars.js
@@ -2,29 +2,36 @@ export async function jBars (from, to, res) {
console.log('[jBars]: Method call', res, from, to);
- var fromDate = new Date(from*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("toDate: ", toDate.toUTCString());
const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443";
-
- // 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 contract = "0xC6962004f452bE9203591991D15f6b388e09E8D0";
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',];
@@ -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 date = is_daily_res ? String(iDate.getUTCDate()).padStart(2, '0') : "";
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);
if (response.ok) {
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 {
+ console.log(`Fetch: file not found: ${url}`)
ohlc = []; // no file, then empty data
}
iFile = new Date(iDate);
@@ -80,14 +92,18 @@ export async function jBars (from, to, res) {
// Skip samples not for our time
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
- const visible_missing_samples = true;
+ const visible_missing_samples = false;
if (ohlcDate == undefined) {
bar = {
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
-
else if ( iDate.getTime() != ohlcDate.getTime() ) {
-
bar = {
time: iDate.getTime(),
}
@@ -114,10 +128,10 @@ export async function jBars (from, to, res) {
low: 0,
close: 0,
});
+ }
// Copy ohlc sample
-
- } else {
+ else {
bar = {
time: iDate.getTime(),
open: ohlc[iohlc][1] ?? ohlc[iohlc][4], // open
diff --git a/src/charts/streaming.js b/src/charts/streaming.js
new file mode 100644
index 0000000..3566e6a
--- /dev/null
+++ b/src/charts/streaming.js
@@ -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);
+;