mirror of https://github.com/ghostfolio/ghostfolio
Thomas Kaul
1 week ago
committed by
GitHub
24 changed files with 614 additions and 200 deletions
@ -0,0 +1,47 @@ |
|||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; |
|||
import { |
|||
DEFAULT_CURRENCY, |
|||
DEFAULT_LANGUAGE_CODE, |
|||
HEADER_KEY_IMPERSONATION |
|||
} from '@ghostfolio/common/config'; |
|||
import { AiPromptResponse } from '@ghostfolio/common/interfaces'; |
|||
import { permissions } from '@ghostfolio/common/permissions'; |
|||
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|||
|
|||
import { Controller, Get, Headers, Inject, UseGuards } from '@nestjs/common'; |
|||
import { REQUEST } from '@nestjs/core'; |
|||
import { AuthGuard } from '@nestjs/passport'; |
|||
|
|||
import { AiService } from './ai.service'; |
|||
|
|||
@Controller('ai') |
|||
export class AiController { |
|||
public constructor( |
|||
private readonly aiService: AiService, |
|||
private readonly impersonationService: ImpersonationService, |
|||
@Inject(REQUEST) private readonly request: RequestWithUser |
|||
) {} |
|||
|
|||
@Get('prompt') |
|||
@HasPermission(permissions.readAiPrompt) |
|||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|||
public async getPrompt( |
|||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId |
|||
): Promise<AiPromptResponse> { |
|||
const impersonationUserId = |
|||
await this.impersonationService.validateImpersonationId(impersonationId); |
|||
|
|||
const prompt = await this.aiService.getPrompt({ |
|||
impersonationId: impersonationUserId, |
|||
languageCode: |
|||
this.request.user.Settings.settings.language ?? DEFAULT_LANGUAGE_CODE, |
|||
userCurrency: |
|||
this.request.user.Settings.settings.baseCurrency ?? DEFAULT_CURRENCY, |
|||
userId: this.request.user.id |
|||
}); |
|||
|
|||
return { prompt }; |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; |
|||
import { AccountService } from '@ghostfolio/api/app/account/account.service'; |
|||
import { OrderModule } from '@ghostfolio/api/app/order/order.module'; |
|||
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; |
|||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; |
|||
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; |
|||
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service'; |
|||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; |
|||
import { UserModule } from '@ghostfolio/api/app/user/user.module'; |
|||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; |
|||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; |
|||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; |
|||
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; |
|||
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; |
|||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module'; |
|||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; |
|||
|
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
import { AiController } from './ai.controller'; |
|||
import { AiService } from './ai.service'; |
|||
|
|||
@Module({ |
|||
controllers: [AiController], |
|||
imports: [ |
|||
ConfigurationModule, |
|||
DataProviderModule, |
|||
ExchangeRateDataModule, |
|||
ImpersonationModule, |
|||
MarketDataModule, |
|||
OrderModule, |
|||
PortfolioSnapshotQueueModule, |
|||
PrismaModule, |
|||
RedisCacheModule, |
|||
SymbolProfileModule, |
|||
UserModule |
|||
], |
|||
providers: [ |
|||
AccountBalanceService, |
|||
AccountService, |
|||
AiService, |
|||
CurrentRateService, |
|||
MarketDataService, |
|||
PortfolioCalculatorFactory, |
|||
PortfolioService, |
|||
RulesService |
|||
] |
|||
}) |
|||
export class AiModule {} |
@ -0,0 +1,60 @@ |
|||
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; |
|||
|
|||
import { Injectable } from '@nestjs/common'; |
|||
|
|||
@Injectable() |
|||
export class AiService { |
|||
public constructor(private readonly portfolioService: PortfolioService) {} |
|||
|
|||
public async getPrompt({ |
|||
impersonationId, |
|||
languageCode, |
|||
userCurrency, |
|||
userId |
|||
}: { |
|||
impersonationId: string; |
|||
languageCode: string; |
|||
userCurrency: string; |
|||
userId: string; |
|||
}) { |
|||
const { holdings } = await this.portfolioService.getDetails({ |
|||
impersonationId, |
|||
userId |
|||
}); |
|||
|
|||
const holdingsTable = [ |
|||
'| Name | Symbol | Currency | Asset Class | Asset Sub Class | Allocation in Percentage |', |
|||
'| --- | --- | --- | --- | --- | --- |', |
|||
...Object.values(holdings) |
|||
.sort((a, b) => { |
|||
return b.allocationInPercentage - a.allocationInPercentage; |
|||
}) |
|||
.map( |
|||
({ |
|||
allocationInPercentage, |
|||
assetClass, |
|||
assetSubClass, |
|||
currency, |
|||
name, |
|||
symbol |
|||
}) => { |
|||
return `| ${name} | ${symbol} | ${currency} | ${assetClass} | ${assetSubClass} | ${(allocationInPercentage * 100).toFixed(3)}% |`; |
|||
} |
|||
) |
|||
]; |
|||
|
|||
return [ |
|||
`You are a neutral financial assistant. Please analyze the following investment portfolio (base currency being ${userCurrency}) in simple words.`, |
|||
...holdingsTable, |
|||
'Structure your answer with these sections:', |
|||
'Overview: Briefly summarize the portfolio’s composition and allocation rationale.', |
|||
'Risk Assessment: Identify potential risks, including market volatility, concentration, and sectoral imbalances.', |
|||
'Advantages: Highlight strengths, focusing on growth potential, diversification, or other benefits.', |
|||
'Disadvantages: Point out weaknesses, such as overexposure or lack of defensive assets.', |
|||
'Target Group: Discuss who this portfolio might suit (e.g., risk tolerance, investment goals, life stages, and experience levels).', |
|||
'Optimization Ideas: Offer ideas to complement the portfolio, ensuring they are constructive and neutral in tone.', |
|||
'Conclusion: Provide a concise summary highlighting key insights.', |
|||
`Provide your answer in the following language: ${languageCode}.` |
|||
].join('\n'); |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
export interface AiPromptResponse { |
|||
prompt: string; |
|||
} |
Loading…
Reference in new issue