finalytics
Version:
Node.js wrapper for finalytics Rust library using FFI
606 lines (573 loc) • 24.3 kB
JavaScript
import ffi from '@2060.io/ffi-napi';
import ref from 'ref-napi';
import Polars from 'nodejs-polars';
import { Chart, dfToJSON, getNativeLibPath } from './utils.js';
// Define C types
const TickerHandle = ref.types.void; // Opaque pointer
const TickerHandlePtr = ref.refType(TickerHandle);
const CharPtr = ref.types.CString;
const CharPtrPtr = ref.refType(CharPtr);
// Load the finalytics library
const lib = ffi.Library(getNativeLibPath(), {
finalytics_ticker_new: [TickerHandlePtr, [
CharPtr, CharPtr, CharPtr, CharPtr, CharPtr,
'double', 'double', CharPtr, CharPtr
]],
finalytics_ticker_free: ['void', [TickerHandlePtr]],
finalytics_free_string: ['void', [CharPtr]],
finalytics_ticker_get_quote: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_get_summary_stats: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_get_price_history: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_get_options_chain: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_get_news: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_get_income_statement: ['int', [TickerHandlePtr, CharPtr, 'int', CharPtrPtr]],
finalytics_ticker_get_balance_sheet: ['int', [TickerHandlePtr, CharPtr, 'int', CharPtrPtr]],
finalytics_ticker_get_cashflow_statement: ['int', [TickerHandlePtr, CharPtr, 'int', CharPtrPtr]],
finalytics_ticker_get_financial_ratios: ['int', [TickerHandlePtr, CharPtr, CharPtrPtr]],
finalytics_ticker_volatility_surface: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_performance_stats: ['int', [TickerHandlePtr, CharPtrPtr]],
finalytics_ticker_performance_chart: ['int', [TickerHandlePtr, 'uint', 'uint', CharPtrPtr]],
finalytics_ticker_candlestick_chart: ['int', [TickerHandlePtr, 'uint', 'uint', CharPtrPtr]],
finalytics_ticker_options_chart: ['int', [TickerHandlePtr, CharPtr, 'uint', 'uint', CharPtrPtr]],
finalytics_ticker_news_sentiment_chart: ['int', [TickerHandlePtr, 'uint', 'uint', CharPtrPtr]],
finalytics_ticker_report: ['int', [TickerHandlePtr, CharPtr, CharPtrPtr]],
});
/**
* Ticker class representing a single financial ticker with methods for retrieving data and analytics.
*/
class Ticker {
/**
* Creates a new Ticker instance.
* @param {Buffer} handle - Opaque pointer to the underlying C TickerHandle.
* @private
*/
constructor(handle) {
this.handle = handle;
}
/**
* Retrieves the latest quote for the ticker.
* @returns {Promise<Object>} A promise resolving to the quote data as a JSON object.
* @throws {Error} If quote retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const quote = await ticker.getQuote();
* console.log(quote);
* ticker.free();
*/
async getQuote() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_quote(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get quote: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(JSON.parse(output));
});
}
/**
* Retrieves summary technical and fundamental statistics for the ticker.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing summary statistics.
* @throws {Error} If stats retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const summary = await ticker.getSummaryStats();
* console.log(summary);
* ticker.free();
*/
async getSummaryStats() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_summary_stats(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get summary stats: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves OHLCV price history for the ticker.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing price history data.
* @throws {Error} If price history retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').startDate('2023-01-01').endDate('2023-12-31').build();
* const history = await ticker.getPriceHistory();
* console.log(history);
* ticker.free();
*/
async getPriceHistory() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_price_history(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get price history: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the options chain for the ticker.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing options chain data.
* @throws {Error} If options chain retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const options = await ticker.getOptionsChain();
* console.log(options);
* ticker.free();
*/
async getOptionsChain() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_options_chain(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get options chain: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the latest news headlines for the ticker.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing news data.
* @throws {Error} If news retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const news = await ticker.getNews();
* console.log(news);
* ticker.free();
*/
async getNews() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_news(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get news: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the income statement for the ticker.
* @param {string} frequency - The frequency of the statement ('annual' or 'quarterly').
* @param {boolean} formatted - Whether to return the statement in a formatted manner.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing income statement data.
* @throws {Error} If income statement retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const income = await ticker.getIncomeStatement('quarterly', true);
* console.log(income);
* ticker.free();
*/
async getIncomeStatement(frequency, formatted) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_income_statement(this.handle, frequency, formatted ? 1 : 0, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get income statement: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the balance sheet for the ticker.
* @param {string} frequency - The frequency of the statement ('annual' or 'quarterly').
* @param {boolean} formatted - Whether to return the statement in a formatted manner.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing balance sheet data.
* @throws {Error} If balance sheet retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const balance = await ticker.getBalanceSheet('quarterly', true);
* console.log(balance);
* ticker.free();
*/
async getBalanceSheet(frequency, formatted) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_balance_sheet(this.handle, frequency, formatted ? 1 : 0, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get balance sheet: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the cash flow statement for the ticker.
* @param {string} frequency - The frequency of the statement ('annual' or 'quarterly').
* @param {boolean} formatted - Whether to return the statement in a formatted manner.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing cash flow statement data.
* @throws {Error} If cash flow statement retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const cashflow = await ticker.getCashflowStatement('quarterly', true);
* console.log(cashflow);
* ticker.free();
*/
async getCashflowStatement(frequency, formatted) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_cashflow_statement(this.handle, frequency, formatted ? 1 : 0, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get cash flow statement: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves financial ratios for the ticker.
* @param {string} frequency - The frequency of the ratios ('annual' or 'quarterly').
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing financial ratios.
* @throws {Error} If financial ratios retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const ratios = await ticker.getFinancialRatios('quarterly');
* console.log(ratios);
* ticker.free();
*/
async getFinancialRatios(frequency) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_get_financial_ratios(this.handle, frequency, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get financial ratios: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves the volatility surface for the ticker.
* @returns {Promise<Polars.DataFrame>} A promise resolving to a Polars DataFrame containing volatility surface data.
* @throws {Error} If volatility surface retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const surface = await ticker.volatilitySurface();
* console.log(surface);
* ticker.free();
*/
async volatilitySurface() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_volatility_surface(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get volatility surface: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(Polars.readJSON(Buffer.from(output)));
});
}
/**
* Retrieves performance statistics for the ticker.
* @returns {Promise<Object>} A promise resolving to a JSON object containing performance statistics.
* @throws {Error} If performance stats retrieval fails.
* @example
* const ticker = await new TickerBuilder()
* .symbol('AAPL')
* .benchmarkSymbol('^GSPC')
* .startDate('2023-01-01')
* .endDate('2023-12-31')
* .interval('1d')
* .riskFreeRate(0.02)
* .build();
* const stats = await ticker.performanceStats();
* console.log(stats);
* ticker.free();
*/
async performanceStats() {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_performance_stats(this.handle, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get performance stats: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(JSON.parse(output));
});
}
/**
* Retrieves the performance chart for the ticker.
* @param {number} [height=0] - The height of the chart (0 for default).
* @param {number} [width=0] - The width of the chart (0 for default).
* @returns {Promise<Chart>} A promise resolving to a Chart instance containing the performance chart.
* @throws {Error} If chart retrieval fails.
* @example
* const ticker = await new TickerBuilder()
* .symbol('AAPL')
* .benchmarkSymbol('^GSPC')
* .startDate('2023-01-01')
* .endDate('2023-12-31')
* .interval('1d')
* .riskFreeRate(0.02)
* .build();
* const chart = await ticker.performanceChart(600, 800);
* chart.show();
* ticker.free();
*/
async performanceChart(height = 0, width = 0) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_performance_chart(this.handle, height, width, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get performance chart: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(new Chart(output));
});
}
/**
* Retrieves the candlestick chart for the ticker.
* @param {number} [height=0] - The height of the chart (0 for default).
* @param {number} [width=0] - The width of the chart (0 for default).
* @returns {Promise<Chart>} A promise resolving to a Chart instance containing the candlestick chart.
* @throws {Error} If chart retrieval fails.
* @example
* const ticker = await new TickerBuilder()
* .symbol('AAPL')
* .startDate('2023-01-01')
* .endDate('2023-12-31')
* .build();
* const chart = await ticker.candlestickChart(600, 800);
* chart.show();
* ticker.free();
*/
async candlestickChart(height = 0, width = 0) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_candlestick_chart(this.handle, height, width, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get candlestick chart: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(new Chart(output));
});
}
/**
* Retrieves the options chart for the ticker.
* @param {string} chartType - The type of options chart to display.
* @param {number} [height=0] - The height of the chart (0 for default).
* @param {number} [width=0] - The width of the chart (0 for default).
* @returns {Promise<Chart>} A promise resolving to a Chart instance containing the options chart.
* @throws {Error} If chart retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const chart = await ticker.optionsChart('volatility', 600, 800);
* chart.show();
* ticker.free();
*/
async optionsChart(chartType, height = 0, width = 0) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_options_chart(this.handle, chartType, height, width, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get options chart: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(new Chart(output));
});
}
/**
* Retrieves the news sentiment chart for the ticker.
* @param {number} [height=0] - The height of the chart (0 for default).
* @param {number} [width=0] - The width of the chart (0 for default).
* @returns {Promise<Chart>} A promise resolving to a Chart instance containing the news sentiment chart.
* @throws {Error} If chart retrieval fails.
* @example
* const ticker = await new TickerBuilder().symbol('AAPL').build();
* const chart = await ticker.newsSentimentChart(600, 800);
* chart.show();
* ticker.free();
*/
async newsSentimentChart(height = 0, width = 0) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_news_sentiment_chart(this.handle, height, width, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get news sentiment chart: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(new Chart(output));
});
}
/**
* Retrieves a comprehensive analytics report for the ticker.
* @param {string} reportType - The type of report to display (e.g., 'performance', 'financials', 'options', 'news').
* @returns {Promise<Chart>} A promise resolving to a Chart instance containing the report.
* @throws {Error} If report retrieval fails.
* @example
* const ticker = await new TickerBuilder()
* .symbol('AAPL')
* .benchmarkSymbol('^GSPC')
* .startDate('2023-01-01')
* .endDate('2023-12-31')
* .interval('1d')
* .riskFreeRate(0.02)
* .build();
* const report = await ticker.report('performance');
* report.show();
* ticker.free();
*/
async report(reportType) {
return new Promise((resolve, reject) => {
const outputPtr = ref.alloc(CharPtrPtr);
const result = lib.finalytics_ticker_report(this.handle, reportType, outputPtr);
if (result !== 0) {
return reject(new Error(`Failed to get report: error code ${result}`));
}
const output = ref.readCString(outputPtr.deref(), 0);
lib.finalytics_free_string(outputPtr.deref());
resolve(new Chart(output));
});
}
/**
* Releases resources associated with the Ticker.
* Should be called when the Ticker is no longer needed to prevent memory leaks.
*/
free() {
if (this.handle) {
lib.finalytics_ticker_free(this.handle);
this.handle = null;
}
}
}
/**
* TickerBuilder class for constructing Ticker instances using the builder pattern.
*/
class TickerBuilder {
/**
* Initializes a new TickerBuilder with default values.
* Defaults:
* - symbol: ''
* - startDate: ''
* - endDate: ''
* - interval: '1d'
* - benchmarkSymbol: ''
* - confidenceLevel: 0.95
* - riskFreeRate: 0.02
* - tickerData: null
* - benchmarkData: null
*/
constructor() {
this.symbolValue = '';
this.startDateValue = '';
this.endDateValue = '';
this.intervalValue = '1d';
this.benchmarkSymbolValue = '';
this.confidenceLevelValue = 0.95;
this.riskFreeRateValue = 0.02;
this.tickerDataValue = null;
this.benchmarkDataValue = null;
}
/**
* Sets the ticker symbol.
* @param {string} value - The ticker symbol (e.g., 'AAPL').
* @returns {TickerBuilder} The builder instance for method chaining.
*/
symbol(value) { this.symbolValue = value; return this; }
/**
* Sets the start date for the data period.
* @param {string} value - The start date in YYYY-MM-DD format.
* @returns {TickerBuilder} The builder instance for method chaining.
*/
startDate(value) { this.startDateValue = value; return this; }
/**
* Sets the end date for the data period.
* @param {string} value - The end date in YYYY-MM-DD format.
* @returns {TickerBuilder} The builder instance for method chaining.
*/
endDate(value) { this.endDateValue = value; return this; }
/**
* Sets the data interval.
* @param {string} value - The data interval (e.g., '2m', '5m', '15m', '30m', '1h', '1d', '1wk', '1mo', '3mo').
* @returns {TickerBuilder} The builder instance for method chaining.
*/
interval(value) { this.intervalValue = value; return this; }
/**
* Sets the benchmark symbol.
* @param {string} value - The benchmark symbol (e.g., '^GSPC').
* @returns {TickerBuilder} The builder instance for method chaining.
*/
benchmarkSymbol(value) { this.benchmarkSymbolValue = value; return this; }
/**
* Sets the confidence level for VaR and ES calculations.
* @param {number} value - The confidence level (e.g., 0.95 for 95% confidence).
* @returns {TickerBuilder} The builder instance for method chaining.
*/
confidenceLevel(value) { this.confidenceLevelValue = value; return this; }
/**
* Sets the risk-free rate for calculations.
* @param {number} value - The risk-free rate (e.g., 0.02 for 2%).
* @returns {TickerBuilder} The builder instance for method chaining.
*/
riskFreeRate(value) { this.riskFreeRateValue = value; return this; }
/**
* Sets custom ticker data.
* @param {Polars.DataFrame|null} value - A Polars DataFrame containing custom ticker data (null if not used).
* @returns {TickerBuilder} The builder instance for method chaining.
*/
tickerData(value) { this.tickerDataValue = value; return this; }
/**
* Sets custom benchmark data.
* @param {Polars.DataFrame|null} value - A Polars DataFrame containing custom benchmark data (null if not used).
* @returns {TickerBuilder} The builder instance for method chaining.
*/
benchmarkData(value) { this.benchmarkDataValue = value; return this; }
/**
* Constructs the Ticker instance with the configured parameters.
* The symbol parameter is required; other parameters are optional and use defaults if not set.
* @returns {Promise<Ticker>} A promise resolving to the initialized Ticker object.
* @throws {Error} If Ticker creation fails or symbol is missing.
* @example
* const ticker = await new TickerBuilder()
* .symbol('AAPL')
* .benchmarkSymbol('^GSPC')
* .startDate('2023-01-01')
* .endDate('2023-12-31')
* .interval('1d')
* .riskFreeRate(0.02)
* .build();
* ticker.free();
*/
async build() {
if (!this.symbolValue) throw new Error('Symbol is required');
const tickerDataJson = this.tickerDataValue ? dfToJSON(this.tickerDataValue) : '';
const benchmarkDataJson = this.benchmarkDataValue ? dfToJSON(this.benchmarkDataValue) : '';
return new Promise((resolve, reject) => {
const handle = lib.finalytics_ticker_new(
this.symbolValue,
this.startDateValue,
this.endDateValue,
this.intervalValue,
this.benchmarkSymbolValue,
this.confidenceLevelValue,
this.riskFreeRateValue,
tickerDataJson || null,
benchmarkDataJson || null
);
if (!handle || handle.isNull()) {
return reject(new Error('Failed to create Ticker'));
}
resolve(new Ticker(handle));
});
}
}
export { Ticker, TickerBuilder };