historical ohlc support, edit tvWidget to enable.

This commit is contained in:
7400
2024-02-08 12:32:44 -08:00
parent 5465efbd86
commit 4ab0cc9659
4 changed files with 277 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
import {useChartOrderStore} from "@/orderbuild.js";
import {invokeCallbacks, prototype} from "@/common.js";
import datafeed from "./datafeed.js";
export let widget = null
export let chart = null
@@ -29,9 +30,11 @@ export function initWidget(el) {
// debug: true,
autosize: true,
symbol: 'AAPL',
// symbol: 'Bitfinex:BTC/USD', // use this for ohlc
interval: '1D',
container: el,
datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"),
// datafeed: datafeed, // use this for ohlc
locale: "en",
disabled_features: [],
enabled_features: [],

166
src/charts/datafeed.js Normal file
View File

@@ -0,0 +1,166 @@
// import {
// makeApiRequest,
// generateSymbol,
// parseFullSymbol,
// } from './helpers.js';
// import {
// subscribeOnStream,
// unsubscribeFromStream,
// } from './streaming.js';
import {jBars} from './jBars.js';
const lastBarsCache = new Map();
// DatafeedConfiguration implementation
const configurationData = {
// Represents the resolutions for bars supported by your datafeed
// supported_resolutions: ['1D', '1W', '1M'],
supported_resolutions: ['1D'],
// The `exchanges` arguments are used for the `searchSymbols` method if a user selects the exchange
exchanges: [{
value: 'Bitfinex',
name: 'Bitfinex',
desc: 'Bitfinex',
},
],
// The `symbols_types` arguments are used for the `searchSymbols` method if a user selects this symbol type
symbols_types: [{
name: 'crypto',
value: 'crypto',
},
],
};
// Obtains all symbols for all exchanges supported by CryptoCompare API
async function getAllSymbols() {
// const data = await makeApiRequest('data/v3/all/exchanges');
// let allSymbols = [];
return [{
symbol: 'BTC/USD',
full_name: 'Bitfinex:BTC/USD',
description: 'BTC/USD',
exchange: 'Bitfinex',
type: 'crypto'}
];
}
export default {
onReady: (callback) => {
console.log('[onReady]: Method call');
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);
},
resolveSymbol: async (
symbolName,
onSymbolResolvedCallback,
onResolveErrorCallback,
extension
) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbols = await getAllSymbols();
const symbolItem = symbols.find(({
full_name,
}) => full_name === symbolName);
if (!symbolItem) {
console.log('[resolveSymbol]: Cannot resolve symbol', symbolName);
onResolveErrorCallback('cannot resolve symbol');
return;
}
// Symbol information object
const symbolInfo = {
ticker: symbolItem.full_name,
name: symbolItem.symbol,
description: symbolItem.description,
type: symbolItem.type,
session: '24x7',
timezone: 'Etc/UTC',
exchange: symbolItem.exchange,
minmov: 1,
pricescale: 100,
has_intraday: false,
has_no_volume: true,
has_weekly_and_monthly: false,
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
console.log('[resolveSymbol]: Symbol resolved', symbolName);
onSymbolResolvedCallback(symbolInfo);
},
getBars: async (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, from, to);
// const parsedSymbol = parseFullSymbol(symbolInfo.full_name);
// const urlParameters = {
// e: parsedSymbol.exchange,
// fsym: parsedSymbol.fromSymbol,
// tsym: parsedSymbol.toSymbol,
// toTs: to,
// limit: 2000,
// };
try {
var bars = await jBars(from, to, resolution);
if (firstDataRequest) {
lastBarsCache.set(symbolInfo.full_name, {
...bars[bars.length - 1],
});
}
console.log(`[getBars]: returned ${bars.length} bar(s)`);
onHistoryCallback(bars, {
noData: false,
});
} catch (error) {
console.log('[getBars]: Get error', error);
onErrorCallback(error);
}
},
subscribeBars: (
symbolInfo,
resolution,
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
// throw Error('subscribeBars unimplemented');
// subscribeOnStream(
// symbolInfo,
// resolution,
// onRealtimeCallback,
// subscriberUID,
// onResetCacheNeededCallback,
// lastBarsCache.get(symbolInfo.full_name),
// );
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
// throw Error('unsubscribeBars unimplemented');
// unsubscribeFromStream(subscriberUID);
},
};

107
src/charts/jBars.js Normal file
View File

@@ -0,0 +1,107 @@
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);
console.log("fromDate:", fromDate.toUTCString());
console.log("toDate: ", toDate.toUTCString());
const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443";
// check parameters
if (res != "1D") throw Error("Only 1D resolution currently supported");
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();
}
var bars = [];
for ( // Once around for each sample in from-to range
let iDate = fromDate,
// loop state
iMonth = -1,
iolhc = 0,
ohlc;
iDate < toDate;
iDate.setUTCDate(iDate.getUTCDate()+1)
) {
let bar = undefined;
// Fetch one sample file as needed
if (iMonth != iDate.getUTCMonth()) {
const yr = iDate.getUTCFullYear();
const mo = String(iDate.getUTCMonth()+1).padStart(2, '0');
const url = `/ohlc/42161/${contract}/${res}/${yr}/${contract}-${res}-${yr}${mo}.json`
let response = await fetch(url);
if (response.ok) {
ohlc = await response.json();
} else {
ohlc = []; // no file, then empty data
}
iMonth = iDate.getUTCMonth();
iolhc = 0;
}
let ohlcDate = iolhc >= ohlc.length ? undefined : new Date(ohlc[iolhc][0]+'Z');
// no ohlc sample, insert a visible sample
if (ohlcDate == undefined) {
bar = {
time: iDate.getTime(),
open: 50,
high: 50,
low: 0,
close: 0,
}
}
// ohlc sample not for this time, insert invisible sample
else if ( iDate.getTime() != ohlcDate.getTime() ) {
bar = {
time: iDate.getTime(),
// open: 100,
// high: 100,
// low: 0,
// close: 0,
}
// Copy ohlc sample
} else {
bar = {
time: iDate.getTime(),
open: ohlc[iolhc][1],
high: ohlc[iolhc][2],
low: ohlc[iolhc][3],
close: ohlc[iolhc][4],
}
iolhc++;
}
if (bar==undefined) throw "bar==undefined";
bars.push(bar);
}
return bars;
}