historical ohlc support, edit tvWidget to enable.
This commit is contained in:
1
public/ohlc
Symbolic link
1
public/ohlc
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../ohlc/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {invokeCallbacks, prototype} from "@/common.js";
|
import {invokeCallbacks, prototype} from "@/common.js";
|
||||||
|
import datafeed from "./datafeed.js";
|
||||||
|
|
||||||
export let widget = null
|
export let widget = null
|
||||||
export let chart = null
|
export let chart = null
|
||||||
@@ -29,9 +30,11 @@ export function initWidget(el) {
|
|||||||
// debug: true,
|
// debug: true,
|
||||||
autosize: true,
|
autosize: true,
|
||||||
symbol: 'AAPL',
|
symbol: 'AAPL',
|
||||||
|
// symbol: 'Bitfinex:BTC/USD', // use this for ohlc
|
||||||
interval: '1D',
|
interval: '1D',
|
||||||
container: el,
|
container: el,
|
||||||
datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"),
|
datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"),
|
||||||
|
// datafeed: datafeed, // use this for ohlc
|
||||||
locale: "en",
|
locale: "en",
|
||||||
disabled_features: [],
|
disabled_features: [],
|
||||||
enabled_features: [],
|
enabled_features: [],
|
||||||
|
|||||||
166
src/charts/datafeed.js
Normal file
166
src/charts/datafeed.js
Normal 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
107
src/charts/jBars.js
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user