Function bodies 106 total
parse_date function · python · L25-L39 (15 LOC)apps/backend/python/efficient_frontier.py
def parse_date(date_input):
"""
Convert string date to datetime object
Args:
date_input: String (YYYY-MM-DD), datetime, or None
Returns:
datetime object or None
"""
if date_input is None:
return None
if isinstance(date_input, str):
return datetime.strptime(date_input, '%Y-%m-%d')
return date_inputvalidate_data_sufficiency function · python · L42-L57 (16 LOC)apps/backend/python/efficient_frontier.py
def validate_data_sufficiency(data, min_points=MIN_DATA_POINTS, data_type="price"):
"""
Validate that we have sufficient data points
Args:
data: DataFrame or Series to validate
min_points: Minimum required data points
data_type: Description of data for error message
Raises:
ValueError if insufficient data
"""
if data.empty or len(data) < min_points:
raise ValueError(
f"Insufficient {data_type} data. Need at least {min_points} points, got {len(data)}"
)calculate_annualized_risk_free_rate function · python · L62-L76 (15 LOC)apps/backend/python/efficient_frontier.py
def calculate_annualized_risk_free_rate(prices, ticker='SGOV'):
"""
Calculate annualized risk-free rate from treasury ETF
Args:
prices: DataFrame with price data
ticker: Treasury ETF ticker (default: SGOV)
Returns:
Annualized risk-free rate (float)
"""
if ticker in prices.columns:
returns = prices[ticker].pct_change().dropna()
return returns.mean() * TRADING_DAYS_PER_YEAR
return DEFAULT_RISK_FREE_RATEcalculate_sharpe_ratio function · python · L79-L93 (15 LOC)apps/backend/python/efficient_frontier.py
def calculate_sharpe_ratio(portfolio_return, portfolio_std, risk_free_rate):
"""
Calculate Sharpe ratio with zero-volatility handling
Args:
portfolio_return: Expected portfolio return
portfolio_std: Portfolio standard deviation
risk_free_rate: Risk-free rate
Returns:
Sharpe ratio (float)
"""
if portfolio_std > 0:
return (portfolio_return - risk_free_rate) / portfolio_std
return 0.0calculate_portfolio_metrics function · python · L96-L117 (22 LOC)apps/backend/python/efficient_frontier.py
def calculate_portfolio_metrics(weights, mu, cov_matrix, risk_free_rate):
"""
Calculate portfolio return, volatility, and Sharpe ratio
Args:
weights: Portfolio weights (numpy array)
mu: Expected returns vector
cov_matrix: Covariance matrix
risk_free_rate: Risk-free rate
Returns:
dict with return, volatility, and sharpeRatio
"""
portfolio_return = np.dot(weights, mu)
portfolio_std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
sharpe = calculate_sharpe_ratio(portfolio_return, portfolio_std, risk_free_rate)
return {
'return': float(portfolio_return),
'volatility': float(portfolio_std),
'sharpeRatio': float(sharpe)
}fetch_price_data function · python · L122-L191 (70 LOC)apps/backend/python/efficient_frontier.py
def fetch_price_data(tickers, start_date=None, end_date=None):
"""
Fetch historical adjusted close prices for given tickers
Args:
tickers: List of ticker symbols
start_date: Start date (string YYYY-MM-DD or datetime, default: 3 years ago)
end_date: End date (string YYYY-MM-DD or datetime, default: today)
Returns:
DataFrame with adjusted close prices
"""
# Parse dates (always provided from frontend)
end_date = parse_date(end_date)
start_date = parse_date(start_date)
# Validate date range
if start_date >= end_date:
raise ValueError(
f"Start date ({start_date.date()}) must be before end date ({end_date.date()})"
)
# Check for future dates
now = datetime.now()
today = now.date()
if end_date > now:
raise ValueError(f"End date ({end_date.date()}) cannot be in the future")
if start_date > now:
raise ValueError(f"Start date ({start_date.date()}) cannot bcalculate_efficient_frontier function · python · L194-L283 (90 LOC)apps/backend/python/efficient_frontier.py
def calculate_efficient_frontier(prices, num_portfolios=500):
"""
Calculate efficient frontier using PyPortfolioOpt
Args:
prices: DataFrame with historical prices
num_portfolios: Number of random portfolios to generate
Returns:
dict with GMV, Max Sharpe, and efficient frontier points
"""
# Calculate expected returns and covariance matrix
mu = expected_returns.mean_historical_return(prices)
S = risk_models.sample_cov(prices)
# Calculate risk-free rate
risk_free_rate = calculate_annualized_risk_free_rate(prices)
# === Global Minimum Variance (GMV) Portfolio ===
ef_gmv = EfficientFrontier(mu, S)
ef_gmv.min_volatility()
gmv_weights = ef_gmv.clean_weights()
gmv_performance = ef_gmv.portfolio_performance(verbose=False, risk_free_rate=risk_free_rate)
# === Maximum Sharpe Ratio Portfolio ===
ef_sharpe = EfficientFrontier(mu, S)
ef_sharpe.max_sharpe(risk_free_rate=risk_free_rate)
sharpe_weigWant this analysis on your repo? https://repobility.com/scan/
fetch_benchmark_prices function · python · L286-L309 (24 LOC)apps/backend/python/efficient_frontier.py
def fetch_benchmark_prices(prices, benchmark_ticker='SPY'):
"""
Fetch or extract benchmark prices
Args:
prices: DataFrame with portfolio price data
benchmark_ticker: Benchmark ticker symbol
Returns:
pandas Series of benchmark prices
"""
if benchmark_ticker in prices.columns:
return prices[benchmark_ticker]
# Fetch benchmark data
start_date = prices.index[0]
end_date = prices.index[-1]
benchmark = yf.Ticker(benchmark_ticker)
benchmark_data = benchmark.history(start=start_date, end=end_date, auto_adjust=True)
if benchmark_data.empty:
raise ValueError(f"Failed to fetch benchmark data for {benchmark_ticker}")
return benchmark_data['Close']calculate_weighted_returns function · python · L312-L334 (23 LOC)apps/backend/python/efficient_frontier.py
def calculate_weighted_returns(prices, portfolio_weights):
"""
Calculate weighted portfolio return series
Args:
prices: DataFrame with historical prices
portfolio_weights: Dict of ticker -> weight
Returns:
pandas Series of portfolio returns
"""
# Get tickers with positive weights
portfolio_tickers = [t for t in portfolio_weights.keys() if portfolio_weights[t] > 0]
if not portfolio_tickers:
return pd.Series(dtype=float)
# Calculate returns for each asset
portfolio_returns = prices[portfolio_tickers].pct_change().dropna()
# Apply weights
weights_array = np.array([portfolio_weights[t] for t in portfolio_tickers])
return portfolio_returns.dot(weights_array)calculate_portfolio_beta function · python · L337-L383 (47 LOC)apps/backend/python/efficient_frontier.py
def calculate_portfolio_beta(prices, portfolio_weights, benchmark_ticker='SPY'):
"""
Calculate portfolio beta against a benchmark using OLS regression
Args:
prices: DataFrame with historical prices
portfolio_weights: Dict of ticker -> weight
benchmark_ticker: Benchmark ticker (default: SPY)
Returns:
float: Portfolio beta
"""
# Get benchmark prices
benchmark_prices = fetch_benchmark_prices(prices, benchmark_ticker)
# Calculate weighted portfolio returns
portfolio_return_series = calculate_weighted_returns(prices, portfolio_weights)
# Check if portfolio is empty
if portfolio_return_series.empty:
return 0.0
# Calculate benchmark returns
benchmark_returns = benchmark_prices.pct_change().dropna()
# Align dates
common_dates = portfolio_return_series.index.intersection(benchmark_returns.index)
# Validate sufficient overlapping data
validate_data_sufficiency(
pd.Series(comcalculate_hedge_sizing function · python · L386-L426 (41 LOC)apps/backend/python/efficient_frontier.py
def calculate_hedge_sizing(portfolio_beta, target_beta, portfolio_value, spy_price):
"""
Calculate hedge sizing for SPY and ES futures
Args:
portfolio_beta: Current portfolio beta
target_beta: Target beta (e.g., 0 for market-neutral)
portfolio_value: Total portfolio value in dollars
spy_price: Current SPY price
Returns:
dict with SPY and ES hedge sizing
"""
# Validate SPY price
if spy_price <= 0:
raise ValueError(
f"Invalid SPY price: {spy_price}. Cannot calculate hedge sizing with zero or negative price."
)
# Calculate required hedge notional
hedge_notional = (portfolio_beta - target_beta) * portfolio_value
# SPY shares to short
spy_shares = int(hedge_notional / spy_price)
spy_notional = spy_shares * spy_price
# ES futures (assuming $50 multiplier and current ES price ~= SPY * 10)
es_multiplier = 50
es_price = spy_price * 10 # Approximate ES price
emain function · python · L429-L518 (90 LOC)apps/backend/python/efficient_frontier.py
def main():
"""
Main function - expects JSON input from stdin
Expected input format:
{
"tickers": ["AAPL", "MSFT", "NVDA"],
"quantities": [10, 20, 15],
"portfolioValue": 100000,
"targetBeta": 0,
"startDate": "2023-01-01",
"endDate": "2024-12-31"
}
"""
try:
# Read input from stdin
input_data = json.loads(sys.stdin.read())
tickers = input_data.get('tickers', [])
quantities = input_data.get('quantities', [])
portfolio_value = input_data.get('portfolioValue', 100000)
target_beta = input_data.get('targetBeta', 0)
start_date = input_data['startDate'] # Required
end_date = input_data['endDate'] # Required
# Validate input
if not tickers:
raise ValueError("No tickers provided")
if len(tickers) != len(quantities):
raise ValueError(
f"Tickers and quantities must have same length. "
AppService.getInfo method · typescript · L5-L12 (8 LOC)apps/backend/src/app.service.ts
getInfo() {
return {
name: 'Glassbox API',
version: '0.1.0',
description: 'Portfolio optimization and beta hedging API',
docs: '/api',
};
}AuthService.signup method · typescript · L18-L92 (75 LOC)apps/backend/src/auth/auth.service.ts
async signup(dto: SignupDto) {
const timestamp = new Date().toISOString();
// Audit log: signup attempt
this.logger.log({
event: 'SIGNUP_ATTEMPT',
email: dto.email,
timestamp,
});
try {
// Check if user already exists
const existingUser = await this.prisma.user.findUnique({
where: { email: dto.email },
});
if (existingUser) {
// Audit log: signup failed (duplicate)
this.logger.warn({
event: 'SIGNUP_FAILED',
email: dto.email,
reason: 'EMAIL_ALREADY_EXISTS',
timestamp,
});
throw new ConflictException('Email already registered');
}
// Hash password
const hashedPassword = await bcrypt.hash(dto.password, this.SALT_ROUNDS);
// Create user
const user = await this.prisma.user.create({
data: {
email: dto.email,
password: hashedPassword,
name: dto.name,
},
select: {
AuthService.login method · typescript · L94-L183 (90 LOC)apps/backend/src/auth/auth.service.ts
async login(dto: LoginDto) {
const timestamp = new Date().toISOString();
// Audit log: login attempt
this.logger.log({
event: 'LOGIN_ATTEMPT',
email: dto.email,
timestamp,
});
try {
// Find user
const user = await this.prisma.user.findUnique({
where: { email: dto.email },
});
if (!user) {
// Audit log: login failed (user not found)
this.logger.warn({
event: 'LOGIN_FAILED',
email: dto.email,
reason: 'USER_NOT_FOUND',
timestamp,
});
throw new UnauthorizedException('Invalid credentials');
}
// Check if user has a password (not OAuth-only user)
if (!user.password) {
// Audit log: login failed (OAuth user trying manual login)
this.logger.warn({
event: 'LOGIN_FAILED',
email: dto.email,
userId: user.id,
reason: 'OAUTH_ONLY_ACCOUNT',
timestamp,
});
tPowered by Repobility — scan your code at https://repobility.com
AuthService.validateUser method · typescript · L185-L195 (11 LOC)apps/backend/src/auth/auth.service.ts
async validateUser(userId: string) {
return this.prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
createdAt: true,
},
});
}AuthService.generateTokens method · typescript · L197-L209 (13 LOC)apps/backend/src/auth/auth.service.ts
private async generateTokens(userId: string, email: string) {
const payload = { sub: userId, email };
const accessToken = await this.jwtService.signAsync(payload, {
expiresIn: '7d',
});
return {
accessToken,
tokenType: 'Bearer',
expiresIn: 604800, // 7 days in seconds
};
}JwtAuthGuard.canActivate method · typescript · L12-L23 (12 LOC)apps/backend/src/auth/jwt-auth.guard.ts
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}JwtStrategy.constructor method · typescript · L10-L36 (27 LOC)apps/backend/src/auth/jwt.strategy.ts
constructor() {
// Validate JWT secret before initializing strategy
const secret = process.env.JWT_SECRET || process.env.NEXTAUTH_SECRET;
if (!secret) {
throw new Error(
'JWT_SECRET or NEXTAUTH_SECRET environment variable must be set. ' +
'Generate a secure secret with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"'
);
}
super({
jwtFromRequest: ExtractJwt.fromExtractors([
// Extract from cookie first
(request: Request) => {
const token = request?.cookies?.accessToken;
if (!token) {
this.logger.warn('No accessToken cookie found in request');
}
return token;
},
// Fallback to Authorization header (for backward compatibility)
ExtractJwt.fromAuthHeaderAsBearerToken(),
]),
ignoreExpiration: false,
secretOrKey: secret,
});
}JwtStrategy.validate method · typescript · L38-L43 (6 LOC)apps/backend/src/auth/jwt.strategy.ts
async validate(payload: any) {
return {
userId: payload.sub || payload.userId,
email: payload.email,
};
}LogAction function · typescript · L14-L43 (30 LOC)apps/backend/src/common/decorators/log-action.decorator.ts
export function LogAction(actionName: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const logger = new Logger(`${target.constructor.name}.${propertyKey}`);
descriptor.value = async function (...args: any[]) {
const request = args[0];
const context = extractContext(request);
const timestamp = new Date().toISOString();
try {
logger.log({ event: `${actionName}_START`, ...context, timestamp });
const result = await originalMethod.apply(this, args);
logger.log({ event: `${actionName}_SUCCESS`, ...context, timestamp });
return result;
} catch (error) {
logger.error({
event: `${actionName}_FAILED`,
...context,
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined,
timestamp,
});
throw erroextractContext function · typescript · L45-L53 (9 LOC)apps/backend/src/common/decorators/log-action.decorator.ts
function extractContext(obj: any): Record<string, any> {
if (!obj || typeof obj !== 'object') return {};
if (obj.user) return { userId: obj.user.userId, email: obj.user.email };
if (obj.email) return { email: obj.email };
if (obj.id) return { id: obj.id };
return {};
}ArrayLengthMatchConstraint.validate method · typescript · L14-L23 (10 LOC)apps/backend/src/common/validators/array-length-match.validator.ts
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
if (!Array.isArray(value) || !Array.isArray(relatedValue)) {
return false;
}
return value.length === relatedValue.length;
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
ArrayLengthMatch function · typescript · L36-L49 (14 LOC)apps/backend/src/common/validators/array-length-match.validator.ts
export function ArrayLengthMatch(
property: string,
validationOptions?: ValidationOptions,
) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [property],
validator: ArrayLengthMatchConstraint,
});
};
}PinoLoggerService.constructor method · typescript · L8-L25 (18 LOC)apps/backend/src/logger/pino-logger.service.ts
constructor() {
this.logger = pino({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport:
process.env.NODE_ENV !== 'production'
? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
singleLine: false,
messageFormat: '{levelLabel} [{time}] {msg}',
ignore: 'pid,hostname',
},
}
: undefined,
});
}bootstrap function · typescript · L9-L70 (62 LOC)apps/backend/src/main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
});
const logger = app.get(PinoLoggerService);
app.useLogger(logger);
// Security headers with Helmet
app.use(helmet({
contentSecurityPolicy: process.env.NODE_ENV === 'production' ? undefined : false,
hsts: {
maxAge: 31536000, // 1 year in seconds
includeSubDomains: true,
preload: true,
},
crossOriginEmbedderPolicy: false, // Allow embedding (if needed for Swagger)
}));
logger.log('Security headers enabled with Helmet');
// Enable cookie parsing
app.use(cookieParser());
// Enable CORS for Next.js frontend
const frontendUrl = process.env.FRONTEND_URL;
if (!frontendUrl) {
logger.warn('FRONTEND_URL not set, defaulting to http://localhost:3000');
}
app.enableCors({
origin: frontendUrl || 'http://localhost:3000',
credentials: true,
});
logger.log(`CORS enabled for origin: ${frontendUrl || 'http://localhostPortfolioController.update method · typescript · L49-L55 (7 LOC)apps/backend/src/portfolio/portfolio.controller.ts
async update(
@Request() req: any,
@Param('id') id: string,
@Body() dto: UpdatePortfolioDto,
) {
return this.portfolioService.update(req.user.userId, id, dto);
}PortfolioService.create method · typescript · L19-L72 (54 LOC)apps/backend/src/portfolio/portfolio.service.ts
async create(userId: string, dto: CreatePortfolioDto): Promise<void> {
try {
// Check for duplicate portfolio name
const existingPortfolio = await this.prisma.portfolio.findFirst({
where: {
userId,
name: dto.name,
},
});
if (existingPortfolio) {
throw new ConflictException('A portfolio with this name already exists');
}
// Extract summary stats if analysis is present
let stats: {
sharpeRatio?: number | null;
volatility?: number | null;
expectedReturn?: number | null;
analysisDate?: string | null;
analysisEndDate?: string | null;
} = {};
if (dto.analysisSnapshot && dto.analysisSnapshot.maxSharpe?.stats) {
stats = {
sharpeRatio: dto.analysisSnapshot.maxSharpe.stats.sharpe,
volatility: dto.analysisSnapshot.maxSharpe.stats.volatility,
expectedReturn: dto.analysisSnapshot.maxSharpe.stats.return,
analPortfolioService.findAll method · typescript · L74-L121 (48 LOC)apps/backend/src/portfolio/portfolio.service.ts
async findAll(userId: string) {
// Fetch portfolios without the large analysisSnapshot JSON
const portfolios = await this.prisma.portfolio.findMany({
where: { userId },
orderBy: { updatedAt: 'desc' },
select: {
id: true,
userId: true,
name: true,
tickers: true,
quantities: true,
sharpeRatio: true,
volatility: true,
expectedReturn: true,
analysisDate: true,
analysisEndDate: true,
createdAt: true,
updatedAt: true,
// Explicitly exclude analysisSnapshot to reduce payload size
},
});
this.logger.log(`Retrieved ${portfolios.length} portfolios for user`);
// Reconstruct the expected structure for frontend
return portfolios.map((p) => {
// If we have stats, reconstruct a "lite" snapshot
let liteSnapshot: Prisma.JsonValue = null;
if (p.sharpeRatio !== null && p.volatility !== null && p.expectedReturn !== null) {
liPortfolioService.findOne method · typescript · L123-L139 (17 LOC)apps/backend/src/portfolio/portfolio.service.ts
async findOne(userId: string, id: string) {
const portfolio = await this.prisma.portfolio.findUnique({
where: { id },
});
if (!portfolio) {
this.logger.warn(`Portfolio not found: ${id}`);
throw new NotFoundException(`Portfolio with ID ${id} not found`);
}
if (portfolio.userId !== userId) {
this.logger.warn(`Unauthorized access attempt to portfolio: ${id}`);
throw new ForbiddenException('You do not have access to this portfolio');
}
return portfolio;
}PortfolioService.update method · typescript · L141-L189 (49 LOC)apps/backend/src/portfolio/portfolio.service.ts
async update(userId: string, id: string, dto: UpdatePortfolioDto) {
try {
// Check existence and ownership first
await this.findOne(userId, id);
// Extract stats if updating snapshot
let stats: {
sharpeRatio?: number | null;
volatility?: number | null;
expectedReturn?: number | null;
analysisDate?: string | null;
analysisEndDate?: string | null;
} = {};
if (dto.analysisSnapshot && dto.analysisSnapshot.maxSharpe?.stats) {
stats = {
sharpeRatio: dto.analysisSnapshot.maxSharpe.stats.sharpe,
volatility: dto.analysisSnapshot.maxSharpe.stats.volatility,
expectedReturn: dto.analysisSnapshot.maxSharpe.stats.return,
analysisDate: dto.analysisSnapshot.analysisDate,
analysisEndDate: dto.analysisSnapshot.analysisEndDate,
};
}
const result = await this.prisma.portfolio.update({
where: { id },
data: {
...(dto.name Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
PortfolioService.remove method · typescript · L191-L205 (15 LOC)apps/backend/src/portfolio/portfolio.service.ts
async remove(userId: string, id: string): Promise<void> {
try {
// Check existence and ownership first
await this.findOne(userId, id);
await this.prisma.portfolio.delete({
where: { id },
});
this.logger.log(`Portfolio deleted: ${id}`);
} catch (error) {
this.logger.error(`Failed to delete portfolio ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}PortfolioService.analyzePortfolio method · typescript · L212-L245 (34 LOC)apps/backend/src/portfolio/portfolio.service.ts
async analyzePortfolio(dto: AnalyzePortfolioDto): Promise<PortfolioAnalysisResultDto> {
this.logger.log(`Analyzing portfolio with ${dto.tickers.length} assets`);
// Set defaults
const portfolioValue = dto.portfolioValue || 100000;
const targetBeta = dto.targetBeta !== undefined ? dto.targetBeta : 0;
const startDate = dto.startDate;
const endDate = dto.endDate;
// Execute Python script
const result = await this.pythonExecutor.executeEfficientFrontier({
tickers: dto.tickers,
quantities: dto.quantities,
portfolioValue,
targetBeta,
startDate,
endDate,
});
this.logger.log('Portfolio analysis completed successfully');
// Return formatted result
return {
gmv: result.gmv,
maxSharpe: result.maxSharpe,
efficientFrontier: result.efficientFrontier,
randomPortfolios: result.randomPortfolios,
portfolioBeta: result.portfolioBeta,
hedging: result.hedging,
riskFreeRate: rePythonExecutorService.executeEfficientFrontier method · typescript · L68-L141 (74 LOC)apps/backend/src/portfolio/python-executor.service.ts
async executeEfficientFrontier(input: PythonExecutorInput): Promise<PythonExecutorResult> {
this.logger.log(`Executing Python script for tickers: ${input.tickers.join(', ')}`);
try {
// Prepare options for PythonShell
const options = {
mode: 'text' as const, // Use text mode to avoid JSON parsing issues
pythonPath: 'python3', // or 'python' depending on system
scriptPath: join(__dirname, '..', '..', 'python'),
args: [],
};
const result = await new Promise<PythonExecutorResult>((resolve, reject) => {
const pyshell = new PythonShell('efficient_frontier.py', options);
// Send input data to Python script via stdin
pyshell.send(JSON.stringify(input));
pyshell.end((err) => {
if (err) {
this.logger.error('Python script execution error:', err);
this.logger.error('Error details:', JSON.stringify(err, null, 2));
reject(new Error(`Python script failedPythonExecutorService.checkPythonEnvironment method · typescript · L147-L162 (16 LOC)apps/backend/src/portfolio/python-executor.service.ts
async checkPythonEnvironment(): Promise<boolean> {
try {
const options = {
mode: 'text' as const,
pythonPath: 'python3',
args: ['--version'],
};
await PythonShell.run('--version', options);
this.logger.log('Python environment is available');
return true;
} catch (error: unknown) {
this.logger.error('Python environment not available:', error instanceof Error ? error.message : String(error));
return false;
}
}TickerService.searchTickers method · typescript · L35-L76 (42 LOC)apps/backend/src/ticker/ticker.service.ts
async searchTickers(query: string): Promise<TickerSearchResult[]> {
if (!query || query.trim().length === 0) {
throw new BadRequestException('Query parameter is required');
}
const sanitizedQuery = query.trim();
const cacheKey = `ticker:search:${sanitizedQuery.toLowerCase()}`;
// Try to get from cache
const cached = await this.cacheManager.get<TickerSearchResult[]>(cacheKey);
if (cached) {
this.logger.debug(`Cache HIT: ${cacheKey}`);
return cached;
}
this.logger.debug(`Cache MISS: ${cacheKey}`);
try {
// Fetch from Yahoo Finance
const results = (await this.yahoo.search(sanitizedQuery)) as YahooFinanceSearchResponse;
this.logger.debug('Yahoo Finance search results:', results);
// Filter and transform results
const filtered: TickerSearchResult[] = results.quotes
.filter((q: YahooFinanceQuote) => q.isYahooFinance !== false && q.quoteType === 'EQUITY') // Only stocks from Yahoo Finance
TickerService.getQuote method · typescript · L78-L100 (23 LOC)apps/backend/src/ticker/ticker.service.ts
async getQuote(ticker: string) {
const cacheKey = `ticker:quote:${ticker.toUpperCase()}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
this.logger.debug(`Cache HIT: ${cacheKey}`);
return cached;
}
this.logger.debug(`Cache MISS: ${cacheKey}`);
try {
const quote = await this.yahoo.quote(ticker);
// Cache quotes for 5 minutes (300 seconds)
await this.cacheManager.set(cacheKey, quote, 300);
return quote;
} catch (error) {
this.logger.error('Error fetching quote from Yahoo Finance:', error);
throw new InternalServerErrorException('Failed to get quote');
}
}TickerService.getHistoricalPrices method · typescript · L102-L131 (30 LOC)apps/backend/src/ticker/ticker.service.ts
async getHistoricalPrices(
ticker: string,
startDate: string,
endDate: string,
) {
const cacheKey = `ticker:history:${ticker}:${startDate}:${endDate}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
this.logger.debug(`Cache HIT: ${cacheKey}`);
return cached;
}
this.logger.debug(`Cache MISS: ${cacheKey}`);
try {
const history = await this.yahoo.historical(ticker, {
period1: startDate,
period2: endDate,
});
// Cache historical data for 24 hours (86400 seconds)
await this.cacheManager.set(cacheKey, history, 86400);
return history;
} catch (error) {
this.logger.error('Error fetching historical prices from Yahoo Finance:', error);
throw new InternalServerErrorException('Failed to get historical prices');
}
}UsersController.changePassword method · typescript · L75-L81 (7 LOC)apps/backend/src/users/users.controller.ts
async changePassword(@Request() req: any, @Body() dto: ChangePasswordDto) {
await this.usersService.changePassword(
req.user.userId,
dto.currentPassword,
dto.newPassword,
);
}Want this analysis on your repo? https://repobility.com/scan/
UsersService.syncUser method · typescript · L29-L79 (51 LOC)apps/backend/src/users/users.service.ts
async syncUser(dto: SyncUserDto) {
const timestamp = new Date().toISOString();
// Audit log: OAuth sync attempt
this.logger.log({
event: 'OAUTH_SYNC_ATTEMPT',
email: dto.email,
timestamp,
});
// Check if user exists
const existingUser = await this.findByEmail(dto.email);
if (existingUser) {
// Audit log: OAuth user updated
this.logger.log({
event: 'OAUTH_USER_UPDATED',
userId: existingUser.id,
email: dto.email,
timestamp,
});
// Update user info if changed
return this.prisma.user.update({
where: { email: dto.email },
data: {
name: dto.name,
googleId: dto.googleId,
},
});
}
// Create new user
const newUser = await this.prisma.user.create({
data: {
email: dto.email,
name: dto.name,
googleId: dto.googleId,
},
});
// Audit log: OAuth user created
this.logger.log({
UsersService.updateUserName method · typescript · L81-L87 (7 LOC)apps/backend/src/users/users.service.ts
async updateUserName(userId: string, name: string) {
this.logger.log(`Updating user ${userId} name to: ${name}`);
return this.prisma.user.update({
where: { id: userId },
data: { name },
});
}UsersService.changePassword method · typescript · L89-L161 (73 LOC)apps/backend/src/users/users.service.ts
async changePassword(
userId: string,
currentPassword: string,
newPassword: string,
) {
const timestamp = new Date().toISOString();
// Fetch user with password
const user = await this.prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
throw new UnauthorizedException('User not found');
}
// Check if user is OAuth user (no password)
if (user.googleId) {
this.logger.warn({
event: 'PASSWORD_CHANGE_OAUTH_ATTEMPT',
userId,
timestamp,
});
throw new BadRequestException(
'Cannot change password for OAuth accounts',
);
}
// Verify user has a password
if (!user.password) {
throw new BadRequestException('User has no password set');
}
// Verify current password
const isCurrentPasswordValid = await bcrypt.compare(
currentPassword,
user.password,
);
if (!isCurrentPasswordValid) {
this.logger.warn({
eventUsersService.deleteUser method · typescript · L163-L188 (26 LOC)apps/backend/src/users/users.service.ts
async deleteUser(userId: string) {
const timestamp = new Date().toISOString();
// Audit log: account deletion
this.logger.warn({
event: 'ACCOUNT_DELETED',
userId,
timestamp,
});
try {
// Prisma will cascade delete portfolios due to onDelete: Cascade in schema
return await this.prisma.user.delete({
where: { id: userId },
});
} catch (error) {
// Audit log: deletion error
this.logger.error({
event: 'ACCOUNT_DELETION_ERROR',
userId,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp,
});
throw error;
}
}HedgingComparison function · typescript · L18-L95 (78 LOC)apps/web/src/app/[lang]/analysis/result/components/HedgingComparison.tsx
export function HedgingComparison({ data }: { data: HedgingProps }) {
const { t } = useTranslation();
// Determine recommendation based on portfolio size/efficiency (simple logic: > $100k implies ES is efficient)
// Since we assume $100k, we'll mark SPY as "Retail Friendly" and ES as "Capital Efficient"
if (!data?.spy || !data?.es) return null;
return (
<div className="grid md:grid-cols-2 gap-6">
{/* SPY Strategy */}
<div className="glass-panel p-6 relative overflow-hidden group hover:border-cyan-400/30 transition-all">
<div className="absolute top-0 right-0 bg-cyan-500/10 px-3 py-1 rounded-bl-xl border-b border-l border-cyan-500/20">
<span className="text-xs font-bold text-cyan-600 dark:text-cyan-400">{t('analysis.hedging.spy.badge')}</span>
</div>
<div className="flex items-center gap-3 mb-6">
<div className="p-3 rounded-xl bg-cyan-500/10 text-cyan-600 dark:text-cyan-400">
<TrendingUp className="w-6MetricCard function · typescript · L15-L37 (23 LOC)apps/web/src/app/[lang]/analysis/result/components/KeyMetrics.tsx
function MetricCard({ label, value, subValue, icon: Icon, color }: MetricProps) {
const colorStyles = {
cyan: 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20',
purple: 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20',
pink: 'bg-pink-500/10 text-pink-600 dark:text-pink-400 border-pink-500/20',
emerald: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20',
};
return (
<div className="glass-panel p-5 flex items-start justify-between group hover:scale-[1.02] transition-transform">
<div>
<p className="text-xs font-semibold text-black/50 dark:text-white/50 uppercase tracking-wider mb-1">{label}</p>
<p className="text-2xl font-bold text-black dark:text-white mb-1">{value}</p>
<p className="text-xs font-medium opacity-70 flex items-center gap-1">
{subValue}
</p>
</div>
<div className={`p-3 rounded-xl border ${colorStyles[color]} tranKeyMetrics function · typescript · L51-L86 (36 LOC)apps/web/src/app/[lang]/analysis/result/components/KeyMetrics.tsx
export function KeyMetrics({ stats }: { stats: PortfolioStats }) {
const { t } = useTranslation();
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label={t('analysis.metrics.return')}
value={`${(stats.maxSharpe.return * 100).toFixed(1)}%`}
subValue={t('analysis.metrics.return-sub')}
icon={TrendingUp}
color="emerald"
/>
<MetricCard
label={t('analysis.metrics.sharpe')}
value={stats.maxSharpe.sharpe.toFixed(2)}
subValue={t('analysis.metrics.sharpe-sub')}
icon={Zap}
color="purple"
/>
<MetricCard
label={t('analysis.metrics.volatility')}
value={`${(stats.maxSharpe.volatility * 100).toFixed(1)}%`}
subValue={`${t('analysis.metrics.volatility-sub')}: ${(stats.gmv.volatility * 100).toFixed(1)}%`}
icon={Activity}
color="pink"
/>
<MetricCard
label={t('analysis.metrics.beta')}
MarketScenarioSimulatorBase function · typescript · L12-L129 (118 LOC)apps/web/src/app/[lang]/analysis/result/components/MarketScenarioSimulator.tsx
function MarketScenarioSimulatorBase({ beta, portfolioValue = 100000 }: MarketScenarioProps) {
const { t } = useTranslation();
// Scenarios: Market moves -5%, 0%, +5%
const marketMoves = [-0.05, 0, 0.05];
const data = marketMoves.map((marketMove) => {
const marketChange = marketMove * 100;
const unhedgedChange = marketMove * beta * 100;
const hedgedChange = 0; // Ideally 0
return {
scenario: marketMove === 0 ? t('analysis.stress-test.flat-market') : marketMove > 0 ? t('analysis.stress-test.market-up') : t('analysis.stress-test.market-down'),
Market: marketChange,
Unhedged: unhedgedChange,
Hedged: hedgedChange,
// For Tooltip Value display
unhedgedValue: portfolioValue * (1 + (marketMove * beta)),
hedgedValue: portfolioValue,
};
});
return (
<div className="glass-panel p-6 space-y-6 hover:bg-cyan-500/5 hover:border-cyan-400/20 dark:hover:bg-cyan-500/10 dark:hover:border-cyan-400/30">
<div className=Powered by Repobility — scan your code at https://repobility.com
SavePortfolioModal function · typescript · L15-L149 (135 LOC)apps/web/src/app/[lang]/analysis/result/components/SavePortfolioModal.tsx
export function SavePortfolioModal({ isOpen, onClose, onSave, error }: SavePortfolioModalProps) {
const { t } = useTranslation();
const [portfolioName, setPortfolioName] = useState('');
const [isLoading, setIsLoading] = useState(false);
// Reset name when modal opens
useEffect(() => {
if (isOpen) {
setPortfolioName('');
}
}, [isOpen]);
// Lock scroll when modal is open
useScrollLock(isOpen);
// Validation (duplicate check is now server-side)
const trimmedName = portfolioName.trim();
const isTooShort = trimmedName.length > 0 && trimmedName.length < 3;
const isTooLong = trimmedName.length > 50;
const isValid = trimmedName.length >= 3 && !isTooLong;
// Keyboard shortcuts
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && isValid && !isLoading) {
e.preventDefault();
handleSubmit();
}
if (e.key === 'Escape') {
e.preventDefault();
onClose();
}
};
const handleSubmit = async (EfficientFrontierChartBase function · typescript · L24-L176 (153 LOC)apps/web/src/app/[lang]/analysis/result/efficient-frontier-chart.tsx
function EfficientFrontierChartBase({ data }: EfficientFrontierChartProps) {
const { t } = useTranslation();
const { efficientFrontier, gmv, maxSharpe, randomPortfolios } = data;
// Format data for chart
const frontierData = efficientFrontier.map((point) => ({
x: point.volatility,
y: point.return,
sharpe: point.sharpeRatio,
name: 'Efficient Frontier',
}));
// Add random portfolios if available (Monte Carlo simulation points)
const randomData = randomPortfolios?.map((point) => ({
x: point.volatility,
y: point.return,
sharpe: point.sharpeRatio,
name: 'Random Portfolio',
})) || [];
const gmvData = {
x: gmv.stats.volatility,
y: gmv.stats.return,
name: 'Global Min Variance',
};
const maxSharpeData = {
x: maxSharpe.stats.volatility,
y: maxSharpe.stats.return,
name: 'Max Sharpe Ratio',
};
// Custom Tooltip
const CustomTooltip = ({ active, payload, t }: any) => {
const debouncedPayload = useDebounce(paCalculationErrorFallback function · typescript · L547-L583 (37 LOC)apps/web/src/app/[lang]/analysis/result/page.tsx
function CalculationErrorFallback() {
const { t } = useTranslation();
return (
<main className="min-h-screen px-6 py-8 flex items-center justify-center">
<div className="glass-panel p-8 max-w-md w-full text-center space-y-6 border-orange-500/20 bg-orange-500/5">
<div className="mx-auto w-16 h-16 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-500">
<AlertCircle className="w-8 h-8" />
</div>
<div>
<h2 className="text-xl font-bold text-black dark:text-white mb-2">
{t('analysis.error.calculation-title')}
</h2>
<p className="text-sm text-black/60 dark:text-white/60">
{t('analysis.error.calculation-message')}
</p>
</div>
<div className="flex gap-3 justify-center">
<Link
href="/portfolio/new"
className="px-4 py-2 rounded-lg bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-whipage 1 / 3next ›