@copytrade/unified-broker
Version:
Unified broker interface library for Indian stock market brokers with plugin architecture
298 lines • 12.1 kB
JavaScript
;
/**
* Fyers Service Adapter
* Adapts the existing FyersService to implement IBrokerService interface
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FyersServiceAdapter = void 0;
const IBrokerService_1 = require("../../interfaces/IBrokerService");
const fyersService_1 = require("../../services/fyersService");
class FyersServiceAdapter extends IBrokerService_1.IBrokerService {
constructor() {
super('fyers');
this.fyersService = new fyersService_1.FyersService();
}
async login(credentials) {
try {
const fyersCredentials = credentials;
// If we have an auth code, complete the OAuth flow
if (fyersCredentials.authCode) {
// Complete OAuth authentication
const serviceCredentials = {
clientId: fyersCredentials.clientId,
secretKey: fyersCredentials.secretKey,
redirectUri: fyersCredentials.redirectUri || '',
authCode: fyersCredentials.authCode,
accessToken: fyersCredentials.accessToken,
refreshToken: fyersCredentials.refreshToken
};
const tokenResponse = await this.fyersService.generateAccessToken(fyersCredentials.authCode, serviceCredentials);
if (tokenResponse.success) {
this.setConnected(true, fyersCredentials.clientId);
return this.createSuccessResponse('OAuth authentication completed', {
accountId: fyersCredentials.clientId,
accessToken: tokenResponse.accessToken
});
}
else {
return this.createErrorResponse(tokenResponse.message || 'OAuth completion failed', tokenResponse);
}
}
else {
// Generate OAuth URL for initial authentication
const serviceCredentials = {
clientId: fyersCredentials.clientId,
secretKey: fyersCredentials.secretKey,
redirectUri: fyersCredentials.redirectUri || '',
authCode: fyersCredentials.authCode,
accessToken: fyersCredentials.accessToken,
refreshToken: fyersCredentials.refreshToken
};
const response = await this.fyersService.login(serviceCredentials);
if (!response.success && response.authUrl) {
// Return OAuth URL for frontend to handle
return {
success: false,
message: response.message || 'OAuth authentication required',
data: {
authUrl: response.authUrl
}
};
}
else {
return this.createErrorResponse(response.message || 'Failed to generate OAuth URL', response);
}
}
}
catch (error) {
return this.createErrorResponse(error.message || 'Login failed', error);
}
}
async logout() {
try {
const result = await this.fyersService.logout();
this.setConnected(false);
// logout returns an object, check if it indicates success
return typeof result === 'object' ? result.success === true : Boolean(result);
}
catch (error) {
console.error('Logout failed:', error);
return false;
}
}
async validateSession(accountId) {
try {
return await this.fyersService.validateSession();
}
catch (error) {
return false;
}
}
async placeOrder(orderRequest) {
try {
// Map unified order type to Fyers-specific format
let fyersOrderType;
switch (orderRequest.orderType) {
case 'LIMIT':
fyersOrderType = 'LIMIT';
break;
case 'MARKET':
fyersOrderType = 'MARKET';
break;
case 'SL-LIMIT':
fyersOrderType = 'SL';
break;
case 'SL-MARKET':
fyersOrderType = 'SL-M';
break;
default:
fyersOrderType = 'MARKET';
}
// Map unified product type to Fyers format
const productTypeMap = {
'CNC': 'CNC',
'MIS': 'INTRADAY',
'NRML': 'MARGIN',
'BO': 'BO',
'C': 'CNC',
'M': 'INTRADAY',
'H': 'MARGIN',
'B': 'BO'
};
const fyersProductType = productTypeMap[orderRequest.productType] || orderRequest.productType;
// Transform to Fyers-specific order format
const fyersOrderRequest = {
symbol: `${orderRequest.exchange}:${orderRequest.symbol}`,
qty: orderRequest.quantity,
type: fyersOrderType,
side: orderRequest.action, // Keep as 'BUY' | 'SELL' string
productType: fyersProductType,
limitPrice: orderRequest.price || 0,
stopPrice: orderRequest.triggerPrice || 0,
validity: (orderRequest.validity === 'GTD' ? 'DAY' : orderRequest.validity),
disclosedQty: 0,
offlineOrder: false,
stopLoss: 0,
takeProfit: 0
};
const response = await this.fyersService.placeOrder(fyersOrderRequest);
// Fyers response format: { s: 'ok'/'error', message: string, id?: string }
if (response.s === 'ok') {
return this.createSuccessResponse('Order placed successfully', {
orderId: response.id,
message: response.message,
brokerOrderId: response.id,
status: 'PLACED'
});
}
else {
return this.createErrorResponse(response.message || 'Order placement failed', response);
}
}
catch (error) {
// Handle token expiry with auto-retry logic
if (error.message?.includes('token') || error.message?.includes('unauthorized')) {
return this.createErrorResponse('Authentication expired. Please reactivate your account.', {
errorType: 'AUTH_EXPIRED',
originalError: error.message
});
}
return this.createErrorResponse(error.message || 'Order placement failed', error);
}
}
async getOrderStatus(accountId, orderId) {
try {
// Fyers doesn't have a separate getOrderStatus method, use getOrderBook
const orderBook = await this.fyersService.getOrderBook();
// orderBook is an array, not an object with orderBook property
if (!Array.isArray(orderBook)) {
throw new Error('Invalid order book response');
}
const order = orderBook.find((o) => o.id === orderId);
if (order) {
return {
orderId: order.id || orderId,
status: order.status || 'UNKNOWN',
quantity: order.qty || 0,
filledQuantity: order.filledQty || 0,
price: order.limitPrice || 0,
averagePrice: order.avgPrice || 0,
timestamp: new Date(order.orderDateTime || Date.now())
};
}
else {
throw new Error(`Order ${orderId} not found`);
}
}
catch (error) {
throw new Error(`Failed to get order status: ${error.message}`);
}
}
async getOrderHistory(accountId) {
try {
// Use getOrderBook for order history in Fyers
const response = await this.fyersService.getOrderBook();
// response is an array, not an object with orderBook property
if (!Array.isArray(response)) {
return [];
}
return response.map((order) => ({
orderId: order.id || '',
status: order.status || 'UNKNOWN',
quantity: order.qty || 0,
filledQuantity: order.filledQty || 0,
price: order.limitPrice || 0,
averagePrice: order.avgPrice || 0,
timestamp: new Date(order.orderDateTime || Date.now())
}));
}
catch (error) {
throw new Error(`Failed to get order history: ${error.message}`);
}
}
async getPositions(accountId) {
try {
const response = await this.fyersService.getPositions();
// response is an array, not an object with netPositions property
if (!Array.isArray(response)) {
return [];
}
return response.map((position) => ({
symbol: position.symbol || '',
quantity: position.netQty || 0,
averagePrice: position.avgPrice || 0,
currentPrice: position.ltp || 0,
pnl: position.pl || 0,
exchange: position.exchange || '',
productType: position.productType || ''
}));
}
catch (error) {
throw new Error(`Failed to get positions: ${error.message}`);
}
}
async getQuote(symbol, exchange) {
try {
const response = await this.fyersService.getQuotes([`${exchange}:${symbol}`]);
if (!response || response.length === 0) {
throw new Error('No quote data received');
}
const quote = response[0];
if (!quote) {
throw new Error('No quote data in response');
}
return {
symbol: quote.symbol || symbol,
price: quote.ltp || 0,
change: quote.chng || 0,
changePercent: quote.chngPercent || 0,
volume: quote.volume || 0,
exchange: exchange,
timestamp: new Date()
};
}
catch (error) {
throw new Error(`Failed to get quote: ${error.message}`);
}
}
async searchSymbols(query, exchange) {
try {
// Fyers doesn't have a searchSymbols method, return empty array for now
console.warn('searchSymbols not implemented for Fyers');
return [];
}
catch (error) {
throw new Error(`Failed to search symbols: ${error.message}`);
}
}
// Fyers-specific methods that can be accessed if needed
getFyersService() {
return this.fyersService;
}
async refreshToken() {
try {
// Fyers refresh token functionality - simplified for now
console.warn('refreshToken not fully implemented for Fyers');
return false;
}
catch (error) {
console.warn('refreshToken not available, using fallback');
return false;
}
}
async completeAuth(authCode) {
try {
// Simplified auth completion - just store the auth code
this.setConnected(true, this.accountId);
return this.createSuccessResponse('Authentication completed', {
accountId: this.accountId,
authCode: authCode
});
}
catch (error) {
return this.createErrorResponse(error.message || 'Authentication failed', error);
}
}
}
exports.FyersServiceAdapter = FyersServiceAdapter;
//# sourceMappingURL=FyersServiceAdapter.js.map