VERSION 1.12 @ 2018-02-14 07:05:47.033501
Cannot read property 'contains' of null #2442 Terminal: custom sorting for account manager tables #2344 Datafeed not closed after removing chart #2270 order.setPrice() has stopped working #2267 Volume in DOM rounded to 0 #2255 Error on bars request and update order lines #2237 Console error: Uncaught (in promise) formatter not received #2228 DOME doesn't work in new version #2211 Add featureset to hide toolbars by default #2209 Add possibility to move studies through z-order #2187 Unexpected resolution values in getBars #2179 Session breaks line is stuck to the left on reload #2153 Typescript declaration has mistakes #2144 Terminal: cannot specify step less than 1 #2141 Namespace the types in charting_library.d.ts #2137 Widget logo showing momentarily on paid account #2132 Add API to apply overrides for created studies #2098 Baseline chart style #2097 How to hide legend "Data Provided by ICE Data services" #2046 Stoch RSI Calculation #2038 Customize loading screen #2012 Supertrend indicator #1950 Translations in market details widget #1946 "Track time" chart setting #1918 Add tabs for positions #1906 Trading Terminal: Notifications Log #1896 Previous Close Price Line #1843 Set Overlay/Compare styles using createStudy/overrides and applyStudiesOverrides #1812 Session under Symbol info is displaying wrong trading interval(s)? #1787 Add "Go to" specific date #1753 Add session breaks #1752 Allow users to specify a volume for the Long/Short Position drawing tools #1691 Add API to use own charts save/load adapter #1679 Drawing toolbar not available in mobile #1673 Typescript definitions #1591 Support for symbols containing lowercase letters #1581 createMultipointShape long_position stop&profit setting #1459 Cannot change awesome oscillator width #1213 New adaptive drawings panel #1145 Edit shapes, studies and series #1101 Hide an indicator with API #1025 VWAP INDICATOR #106
This commit is contained in:
355
datafeeds/udf/src/udf-compatible-datafeed-base.ts
Normal file
355
datafeeds/udf/src/udf-compatible-datafeed-base.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
import {
|
||||
DatafeedConfiguration,
|
||||
ErrorCallback,
|
||||
GetMarksCallback,
|
||||
HistoryCallback,
|
||||
HistoryDepth,
|
||||
IDatafeedChartApi,
|
||||
IDatafeedQuotesApi,
|
||||
IExternalDatafeed,
|
||||
LibrarySymbolInfo,
|
||||
Mark,
|
||||
OnReadyCallback,
|
||||
QuotesCallback,
|
||||
ResolutionBackValues,
|
||||
ResolutionString,
|
||||
ResolveCallback,
|
||||
SearchSymbolResultItem,
|
||||
SearchSymbolsCallback,
|
||||
ServerTimeCallback,
|
||||
SubscribeBarsCallback,
|
||||
TimescaleMark,
|
||||
} from '../../../charting_library/datafeed-api';
|
||||
|
||||
import {
|
||||
getErrorMessage,
|
||||
logMessage,
|
||||
RequestParams,
|
||||
UdfErrorResponse,
|
||||
} from './helpers';
|
||||
|
||||
import {
|
||||
GetBarsResult,
|
||||
HistoryProvider,
|
||||
} from './history-provider';
|
||||
|
||||
import { IQuotesProvider } from './iquotes-provider';
|
||||
import { DataPulseProvider } from './data-pulse-provider';
|
||||
import { QuotesPulseProvider } from './quotes-pulse-provider';
|
||||
import { SymbolsStorage } from './symbols-storage';
|
||||
import { Requester } from './requester';
|
||||
|
||||
export interface UdfCompatibleConfiguration extends DatafeedConfiguration {
|
||||
// tslint:disable
|
||||
supports_search?: boolean;
|
||||
supports_group_request?: boolean;
|
||||
// tslint:enable
|
||||
}
|
||||
|
||||
export interface ResolveSymbolResponse extends LibrarySymbolInfo {
|
||||
s: undefined;
|
||||
}
|
||||
|
||||
// it is hack to let's TypeScript make code flow analysis
|
||||
export interface UdfSearchSymbolsResponse extends Array<SearchSymbolResultItem> {
|
||||
s?: undefined;
|
||||
}
|
||||
|
||||
export const enum Constants {
|
||||
SearchItemsLimit = 30,
|
||||
}
|
||||
|
||||
type UdfDatafeedMarkType<T extends TimescaleMark | Mark> = {
|
||||
[K in keyof T]: T[K] | T[K][];
|
||||
} & {
|
||||
id: (string | number)[];
|
||||
};
|
||||
|
||||
type UdfDatafeedMark = UdfDatafeedMarkType<Mark>;
|
||||
type UdfDatafeedTimescaleMark = UdfDatafeedMarkType<TimescaleMark>;
|
||||
|
||||
function extractField<Field extends keyof Mark>(data: UdfDatafeedMark, field: Field, arrayIndex: number): Mark[Field];
|
||||
function extractField<Field extends keyof TimescaleMark>(data: UdfDatafeedTimescaleMark, field: Field, arrayIndex: number): TimescaleMark[Field];
|
||||
function extractField<Field extends keyof (TimescaleMark & Mark)>(data: UdfDatafeedMark & UdfDatafeedTimescaleMark, field: Field, arrayIndex: number): (TimescaleMark & Mark)[Field] {
|
||||
const value = data[field];
|
||||
return Array.isArray(value) ? value[arrayIndex] : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements interaction with UDF-compatible datafeed.
|
||||
* See UDF protocol reference at https://github.com/tradingview/charting_library/wiki/UDF
|
||||
*/
|
||||
export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQuotesApi, IDatafeedChartApi {
|
||||
protected _configuration: UdfCompatibleConfiguration = defaultConfiguration();
|
||||
private readonly _datafeedURL: string;
|
||||
private readonly _configurationReadyPromise: Promise<void>;
|
||||
|
||||
private _symbolsStorage: SymbolsStorage | null = null;
|
||||
|
||||
private readonly _historyProvider: HistoryProvider;
|
||||
private readonly _dataPulseProvider: DataPulseProvider;
|
||||
|
||||
private readonly _quotesProvider: IQuotesProvider;
|
||||
private readonly _quotesPulseProvider: QuotesPulseProvider;
|
||||
|
||||
private readonly _requester: Requester;
|
||||
|
||||
protected constructor(datafeedURL: string, quotesProvider: IQuotesProvider, requester: Requester, updateFrequency: number = 10 * 1000) {
|
||||
this._datafeedURL = datafeedURL;
|
||||
this._requester = requester;
|
||||
this._historyProvider = new HistoryProvider(datafeedURL, this._requester);
|
||||
this._quotesProvider = quotesProvider;
|
||||
|
||||
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
|
||||
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
|
||||
|
||||
this._configurationReadyPromise = this._requestConfiguration()
|
||||
.then((configuration: UdfCompatibleConfiguration | null) => {
|
||||
if (configuration === null) {
|
||||
configuration = defaultConfiguration();
|
||||
}
|
||||
|
||||
this._setupWithConfiguration(configuration);
|
||||
});
|
||||
}
|
||||
|
||||
public onReady(callback: OnReadyCallback): void {
|
||||
this._configurationReadyPromise.then(() => {
|
||||
callback(this._configuration);
|
||||
});
|
||||
}
|
||||
|
||||
public getQuotes(symbols: string[], onDataCallback: QuotesCallback, onErrorCallback: (msg: string) => void): void {
|
||||
this._quotesProvider.getQuotes(symbols).then(onDataCallback).catch(onErrorCallback);
|
||||
}
|
||||
|
||||
public subscribeQuotes(symbols: string[], fastSymbols: string[], onRealtimeCallback: QuotesCallback, listenerGuid: string): void {
|
||||
this._quotesPulseProvider.subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid);
|
||||
}
|
||||
|
||||
public unsubscribeQuotes(listenerGuid: string): void {
|
||||
this._quotesPulseProvider.unsubscribeQuotes(listenerGuid);
|
||||
}
|
||||
|
||||
public calculateHistoryDepth(resolution: ResolutionString, resolutionBack: ResolutionBackValues, intervalBack: number): HistoryDepth | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getMarks(symbolInfo: LibrarySymbolInfo, startDate: number, endDate: number, onDataCallback: GetMarksCallback<Mark>, resolution: ResolutionString): void {
|
||||
if (!this._configuration.supports_marks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestParams: RequestParams = {
|
||||
symbol: symbolInfo.ticker || '',
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
resolution: resolution,
|
||||
};
|
||||
|
||||
this._send('marks', requestParams)
|
||||
.then((response: Mark[] | UdfDatafeedMark) => {
|
||||
if (!Array.isArray(response)) {
|
||||
const result: Mark[] = [];
|
||||
for (let i = 0; i < response.id.length; ++i) {
|
||||
result.push({
|
||||
id: extractField(response, 'id', i),
|
||||
time: extractField(response, 'time', i),
|
||||
color: extractField(response, 'color', i),
|
||||
text: extractField(response, 'text', i),
|
||||
label: extractField(response, 'label', i),
|
||||
labelFontColor: extractField(response, 'labelFontColor', i),
|
||||
minSize: extractField(response, 'minSize', i),
|
||||
});
|
||||
}
|
||||
|
||||
response = result;
|
||||
}
|
||||
|
||||
onDataCallback(response);
|
||||
})
|
||||
.catch((error?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Request marks failed: ${getErrorMessage(error)}`);
|
||||
onDataCallback([]);
|
||||
});
|
||||
}
|
||||
|
||||
public getTimescaleMarks(symbolInfo: LibrarySymbolInfo, startDate: number, endDate: number, onDataCallback: GetMarksCallback<TimescaleMark>, resolution: ResolutionString): void {
|
||||
if (!this._configuration.supports_timescale_marks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestParams: RequestParams = {
|
||||
symbol: symbolInfo.ticker || '',
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
resolution: resolution,
|
||||
};
|
||||
|
||||
this._send('timescale_marks', requestParams)
|
||||
.then((response: TimescaleMark[] | UdfDatafeedTimescaleMark) => {
|
||||
if (!Array.isArray(response)) {
|
||||
const result: TimescaleMark[] = [];
|
||||
for (let i = 0; i < response.id.length; ++i) {
|
||||
result.push({
|
||||
id: extractField(response, 'id', i),
|
||||
time: extractField(response, 'time', i),
|
||||
color: extractField(response, 'color', i),
|
||||
label: extractField(response, 'label', i),
|
||||
tooltip: extractField(response, 'tooltip', i),
|
||||
});
|
||||
}
|
||||
|
||||
response = result;
|
||||
}
|
||||
|
||||
onDataCallback(response);
|
||||
})
|
||||
.catch((error?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Request timescale marks failed: ${getErrorMessage(error)}`);
|
||||
onDataCallback([]);
|
||||
});
|
||||
}
|
||||
|
||||
public getServerTime(callback: ServerTimeCallback): void {
|
||||
if (!this._configuration.supports_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._send('time')
|
||||
.then((response: string) => {
|
||||
const time = parseInt(response);
|
||||
if (!isNaN(time)) {
|
||||
callback(time);
|
||||
}
|
||||
})
|
||||
.catch((error?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Fail to load server time, error=${getErrorMessage(error)}`);
|
||||
});
|
||||
}
|
||||
|
||||
public searchSymbols(userInput: string, exchange: string, symbolType: string, onResult: SearchSymbolsCallback): void {
|
||||
if (this._configuration.supports_search) {
|
||||
const params: RequestParams = {
|
||||
limit: Constants.SearchItemsLimit,
|
||||
query: userInput.toUpperCase(),
|
||||
type: symbolType,
|
||||
exchange: exchange,
|
||||
};
|
||||
|
||||
this._send('search', params)
|
||||
.then((response: UdfSearchSymbolsResponse | UdfErrorResponse) => {
|
||||
if (response.s !== undefined) {
|
||||
logMessage(`UdfCompatibleDatafeed: search symbols error=${response.errmsg}`);
|
||||
onResult([]);
|
||||
return;
|
||||
}
|
||||
|
||||
onResult(response);
|
||||
})
|
||||
.catch((reason?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Search symbols for '${userInput}' failed. Error=${getErrorMessage(reason)}`);
|
||||
onResult([]);
|
||||
});
|
||||
} else {
|
||||
if (this._symbolsStorage === null) {
|
||||
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
||||
}
|
||||
|
||||
this._symbolsStorage.searchSymbols(userInput, exchange, symbolType, Constants.SearchItemsLimit)
|
||||
.then(onResult)
|
||||
.catch(onResult.bind(null, []));
|
||||
}
|
||||
}
|
||||
|
||||
public resolveSymbol(symbolName: string, onResolve: ResolveCallback, onError: ErrorCallback): void {
|
||||
logMessage('Resolve requested');
|
||||
|
||||
const resolveRequestStartTime = Date.now();
|
||||
function onResultReady(symbolInfo: LibrarySymbolInfo): void {
|
||||
logMessage(`Symbol resolved: ${Date.now() - resolveRequestStartTime}ms`);
|
||||
onResolve(symbolInfo);
|
||||
}
|
||||
|
||||
if (!this._configuration.supports_group_request) {
|
||||
const params: RequestParams = {
|
||||
symbol: symbolName,
|
||||
};
|
||||
|
||||
this._send('symbols', params)
|
||||
.then((response: ResolveSymbolResponse | UdfErrorResponse) => {
|
||||
if (response.s !== undefined) {
|
||||
onError('unknown_symbol');
|
||||
} else {
|
||||
onResultReady(response);
|
||||
}
|
||||
})
|
||||
.catch((reason?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Error resolving symbol: ${getErrorMessage(reason)}`);
|
||||
onError('unknown_symbol');
|
||||
});
|
||||
} else {
|
||||
if (this._symbolsStorage === null) {
|
||||
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
||||
}
|
||||
|
||||
this._symbolsStorage.resolveSymbol(symbolName).then(onResultReady).catch(onError);
|
||||
}
|
||||
}
|
||||
|
||||
public getBars(symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, rangeStartDate: number, rangeEndDate: number, onResult: HistoryCallback, onError: ErrorCallback): void {
|
||||
this._historyProvider.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate)
|
||||
.then((result: GetBarsResult) => {
|
||||
onResult(result.bars, result.meta);
|
||||
})
|
||||
.catch(onError);
|
||||
}
|
||||
|
||||
public subscribeBars(symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, onTick: SubscribeBarsCallback, listenerGuid: string, onResetCacheNeededCallback: () => void): void {
|
||||
this._dataPulseProvider.subscribeBars(symbolInfo, resolution, onTick, listenerGuid);
|
||||
}
|
||||
|
||||
public unsubscribeBars(listenerGuid: string): void {
|
||||
this._dataPulseProvider.unsubscribeBars(listenerGuid);
|
||||
}
|
||||
|
||||
protected _requestConfiguration(): Promise<UdfCompatibleConfiguration | null> {
|
||||
return this._send<UdfCompatibleConfiguration>('config')
|
||||
.catch((reason?: string | Error) => {
|
||||
logMessage(`UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=${getErrorMessage(reason)}`);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private _send<T>(urlPath: string, params?: RequestParams): Promise<T> {
|
||||
return this._requester.sendRequest<T>(this._datafeedURL, urlPath, params);
|
||||
}
|
||||
|
||||
private _setupWithConfiguration(configurationData: UdfCompatibleConfiguration): void {
|
||||
this._configuration = configurationData;
|
||||
|
||||
if (configurationData.exchanges === undefined) {
|
||||
configurationData.exchanges = [];
|
||||
}
|
||||
|
||||
if (!configurationData.supports_search && !configurationData.supports_group_request) {
|
||||
throw new Error('Unsupported datafeed configuration. Must either support search, or support group request');
|
||||
}
|
||||
|
||||
if (configurationData.supports_group_request || !configurationData.supports_search) {
|
||||
this._symbolsStorage = new SymbolsStorage(this._datafeedURL, configurationData.supported_resolutions || [], this._requester);
|
||||
}
|
||||
|
||||
logMessage(`UdfCompatibleDatafeed: Initialized with ${JSON.stringify(configurationData)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function defaultConfiguration(): UdfCompatibleConfiguration {
|
||||
return {
|
||||
supports_search: false,
|
||||
supports_group_request: true,
|
||||
supported_resolutions: ['1', '5', '15', '30', '60', '1D', '1W', '1M'],
|
||||
supports_marks: false,
|
||||
supports_timescale_marks: false,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user