institutional-trader/backend/src/services/pythonServiceClient.js

157 lines
4.1 KiB
JavaScript

/**
* Python Service Client
* Handles communication with the Python processing service
*/
// Use native fetch (Node.js 18+) or node-fetch for older versions
let fetch;
if (typeof globalThis.fetch !== 'undefined') {
// Node.js 18+ has native fetch
fetch = globalThis.fetch;
} else {
// Fallback: dynamically import node-fetch
try {
const nodeFetch = require('node-fetch');
fetch = nodeFetch.default || nodeFetch;
} catch (e) {
// If node-fetch is not available, we'll handle it in the functions
fetch = null;
}
}
const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || 'http://localhost:8010';
/**
* Call Python service for options flow processing
* @param {Object} params - Query parameters
* @returns {Promise<Object>} Processed options flow data
*/
export async function getOptionsFlowFromPython(params = {}) {
if (!fetch) {
throw new Error('Fetch is not available. Please install node-fetch or use Node.js 18+');
}
const {
startDate,
endDate,
minPremium = 80000,
tolPct = 0.20
} = params;
const queryParams = new URLSearchParams();
if (startDate) queryParams.append('start_date', startDate);
if (endDate) queryParams.append('end_date', endDate);
if (minPremium) queryParams.append('min_premium', minPremium);
if (tolPct) queryParams.append('tol_pct', tolPct);
const url = `${PYTHON_SERVICE_URL}/api/options-flow?${queryParams.toString()}`;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Python service error: ${response.status} - ${errorText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error calling Python service:', error);
// Fallback to SQL if Python service is unavailable
if (error.name === 'AbortError' ||
error.code === 'ECONNREFUSED' ||
error.code === 'ETIMEDOUT' ||
error.message.includes('fetch')) {
console.warn('Python service unavailable, falling back to SQL');
throw new Error('PYTHON_SERVICE_UNAVAILABLE');
}
throw error;
}
}
/**
* Check if Python service is healthy
* @returns {Promise<boolean>}
*/
export async function checkPythonServiceHealth() {
if (!fetch) {
return false;
}
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(`${PYTHON_SERVICE_URL}/health`, {
method: 'GET',
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
return false;
}
const data = await response.json();
return data.status === 'healthy';
} catch (error) {
console.warn('Python service health check failed:', error.message);
return false;
}
}
/**
* Get flow statistics from Python service
* @param {string} symbol - Optional symbol filter
* @returns {Promise<Object>}
*/
export async function getFlowStatsFromPython(symbol = null) {
if (!fetch) {
throw new Error('Fetch is not available');
}
const queryParams = new URLSearchParams();
if (symbol) queryParams.append('symbol', symbol);
const url = `${PYTHON_SERVICE_URL}/api/options-flow/stats?${queryParams.toString()}`;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`Python service error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error getting flow stats from Python service:', error);
throw error;
}
}