import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsIn, IsInt, IsOptional, IsString, Matches, MaxLength, Min, MinLength, } from 'class-validator'; import { MoneyDto } from '../../../../../common/money.dto'; import { MAX_INSTRUMENT_QUERY_LENGTH, MIN_INSTRUMENT_QUERY_LENGTH, } from '../../../instrument-query'; import { InvestmentReadQueryDto, InvestmentResourceMetaDto, } from '../../../investment-resource.dto'; import { PortfolioPositionDto } from '../../../portfolio/portfolio.api'; const MIN_TICKER_LENGTH = 1; const MAX_SCREENER_QUERY_LENGTH = 80; const MIN_PAGE = 1; export class StockScreenerCriteriaDto { @ApiPropertyOptional({ maxLength: MAX_SCREENER_QUERY_LENGTH }) @Transform(({ value }: { value: unknown }) => value === '' ? undefined : value, ) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) @MaxLength(MAX_SCREENER_QUERY_LENGTH) query?: string; @ApiPropertyOptional({ minLength: MIN_TICKER_LENGTH }) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) sector?: string; @ApiPropertyOptional({ minLength: MIN_TICKER_LENGTH }) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) shareType?: string; @ApiPropertyOptional({ minLength: MIN_TICKER_LENGTH }) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) currency?: string; @ApiPropertyOptional({ minLength: MIN_TICKER_LENGTH }) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) realExchange?: string; @ApiPropertyOptional({ minLength: MIN_TICKER_LENGTH }) @IsOptional() @IsString() @MinLength(MIN_TICKER_LENGTH) countryOfRisk?: string; @ApiPropertyOptional({ minimum: MIN_TICKER_LENGTH, type: 'integer' }) @IsOptional() @IsInt() @Min(MIN_TICKER_LENGTH) maxLot?: number; } export class StockScreenerPageQueryDto { @ApiProperty({ minimum: MIN_PAGE, type: 'integer' }) @Transform(({ value }: { value: unknown }) => Number(value)) @IsInt() @Min(MIN_PAGE) declare page: number; } export class StockScreenerItemDto { @ApiProperty({ format: 'uuid' }) declare instrumentUid: string; @ApiProperty() declare name: string; @ApiProperty({ nullable: true, type: String }) declare ticker: null | string; @ApiProperty({ nullable: true, type: String }) declare isin: null | string; @ApiProperty({ nullable: true, type: String }) declare logoName: null | string; @ApiProperty({ nullable: true, type: String }) declare shareType: null | string; @ApiProperty({ nullable: true, type: String }) declare sector: null | string; @ApiProperty({ nullable: true, type: String }) declare currency: null | string; @ApiProperty({ nullable: true, type: String }) declare realExchange: null | string; @ApiProperty({ nullable: true, type: String }) declare countryOfRisk: null | string; @ApiProperty({ nullable: true, minimum: MIN_TICKER_LENGTH, type: 'integer' }) declare lot: null | number; } export class StockScreenerPageDto { @ApiProperty() declare searchId: string; @ApiProperty({ format: 'date-time' }) declare asOf: string; @ApiProperty({ minimum: MIN_PAGE, type: 'integer' }) declare page: number; @ApiProperty() declare hasNext: boolean; @ApiProperty({ type: StockScreenerItemDto, isArray: true, maxItems: 30 }) declare items: StockScreenerItemDto[]; } export class StockSearchQueryDto extends InvestmentReadQueryDto { @ApiProperty({ minLength: MIN_INSTRUMENT_QUERY_LENGTH, maxLength: MAX_INSTRUMENT_QUERY_LENGTH }) @Transform(({ value }: { value: unknown }) => typeof value === 'string' ? value.trim() : value, ) @IsString() @MinLength(MIN_INSTRUMENT_QUERY_LENGTH) @MaxLength(MAX_INSTRUMENT_QUERY_LENGTH) declare query: string; } export class StockTickerParamsDto { @ApiProperty({ minLength: MIN_TICKER_LENGTH, maxLength: MAX_INSTRUMENT_QUERY_LENGTH, pattern: '^[A-Za-z0-9][A-Za-z0-9._@+-]*$', }) @IsString() @MinLength(MIN_TICKER_LENGTH) @MaxLength(MAX_INSTRUMENT_QUERY_LENGTH) @Matches(/^[A-Z0-9][\w.@+-]*$/i) declare ticker: string; } export class StockCandlesQueryDto extends InvestmentReadQueryDto { @ApiPropertyOptional({ enum: ['month', 'halfYear', 'year', 'all'], default: 'month', }) @IsOptional() @IsIn(['month', 'halfYear', 'year', 'all']) period?: 'all' | 'halfYear' | 'month' | 'year'; } export class StockDto { @ApiProperty({ format: 'uuid' }) declare instrumentUid: string; @ApiProperty({ description: 'Unambiguous ticker URL segment, or the exact instrument UID.', }) declare slug: string; @ApiProperty() declare name: string; @ApiProperty({ type: String, nullable: true }) declare isin: null | string; @ApiProperty({ type: String, nullable: true }) declare classCode: null | string; @ApiProperty({ type: String, nullable: true }) declare realExchange: null | string; @ApiProperty({ type: Boolean, nullable: true }) declare apiTradeAvailable: boolean | null; @ApiProperty({ type: String, nullable: true }) declare ticker: null | string; @ApiProperty({ type: String, nullable: true }) declare logoName: null | string; @ApiProperty({ type: String, nullable: true }) declare currency: null | string; @ApiProperty({ type: String, nullable: true }) declare shareType: null | string; @ApiProperty({ type: String, nullable: true }) declare sector: null | string; @ApiProperty({ type: Number, nullable: true }) declare lot: null | number; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) declare historyFrom: null | string; } export class StockSearchResultDto extends StockDto { @ApiProperty({ type: MoneyDto, nullable: true }) declare price: MoneyDto | null; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) declare priceAt: null | string; } export class StockQuoteDto { @ApiProperty({ type: MoneyDto, nullable: true }) declare price: MoneyDto | null; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) declare priceAt: null | string; @ApiProperty({ type: String, nullable: true }) declare tradingStatus: null | string; @ApiProperty({ type: MoneyDto, nullable: true }) declare change: MoneyDto | null; @ApiProperty({ type: String, nullable: true }) declare changePercent: null | string; @ApiProperty({ type: String, nullable: true, format: 'date' }) declare previousCloseDate: null | string; @ApiProperty({ type: MoneyDto, nullable: true }) declare previousClose: MoneyDto | null; @ApiProperty({ type: String, isArray: true }) declare unavailable: string[]; @ApiProperty({ format: 'date-time' }) declare observedAt: string; } export class StockCandleDto { @ApiProperty({ format: 'date-time' }) declare time: string; @ApiProperty() declare close: string; @ApiProperty() declare isComplete: boolean; } export class StockCandlesDto { @ApiProperty({ type: StockCandleDto, isArray: true }) declare candles: StockCandleDto[]; @ApiProperty({ type: String, nullable: true }) declare currency: null | string; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) declare historyFrom: null | string; @ApiProperty({ format: 'date-time' }) declare observedAt: string; } export class StockPositionRowDto extends PortfolioPositionDto { @ApiProperty({ type: String, nullable: true }) declare returnPercent: null | string; } export class StockPositionDto { @ApiProperty() declare quantity: string; @ApiProperty({ type: MoneyDto, nullable: true }) declare currentValue: MoneyDto | null; @ApiProperty({ type: MoneyDto, nullable: true }) declare averagePurchasePrice: MoneyDto | null; @ApiProperty({ type: MoneyDto, nullable: true }) declare absoluteReturn: MoneyDto | null; @ApiProperty({ type: String, nullable: true }) declare returnPercent: null | string; @ApiProperty({ type: StockPositionRowDto, isArray: true }) declare accounts: StockPositionRowDto[]; @ApiProperty() declare isStale: boolean; @ApiProperty({ format: 'date-time' }) declare observedAt: string; } export class StockMetricDto { @ApiProperty() declare key: string; @ApiProperty() declare value: number; @ApiProperty({ enum: ['money', 'ratio', 'percent'] }) declare unit: 'money' | 'percent' | 'ratio'; @ApiProperty({ type: String, nullable: true }) declare currency: null | string; @ApiProperty({ type: String, nullable: true, enum: ['TTM', 'MRQ'] }) declare period: 'MRQ' | 'TTM' | null; } export class StockCompanyDto { @ApiProperty({ type: String, nullable: true }) declare description: null | string; @ApiProperty({ type: StockMetricDto, isArray: true }) declare metrics: StockMetricDto[]; @ApiProperty({ type: String, isArray: true }) declare unavailable: string[]; @ApiProperty({ format: 'date-time' }) declare observedAt: string; } export class StockDividendDto { @ApiProperty({ type: MoneyDto, nullable: true }) declare perShare: MoneyDto | null; @ApiProperty({ type: String, nullable: true, format: 'date' }) declare recordDate: null | string; @ApiProperty({ type: String, nullable: true, format: 'date' }) declare lastBuyDate: null | string; @ApiProperty({ type: String, nullable: true, format: 'date' }) declare paymentDate: null | string; @ApiProperty({ enum: ['upcoming', 'past', 'undated'] }) declare timing: 'past' | 'undated' | 'upcoming'; @ApiProperty({ type: MoneyDto, nullable: true }) declare estimatedAmount: MoneyDto | null; } export class StockDividendsDto { @ApiProperty({ type: StockDividendDto, isArray: true }) declare dividends: StockDividendDto[]; @ApiProperty({ format: 'date' }) declare from: string; @ApiProperty({ format: 'date' }) declare to: string; @ApiProperty({ format: 'date-time' }) declare observedAt: string; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) declare positionAt: null | string; @ApiProperty() declare estimateUnavailable: boolean; @ApiProperty() declare positionIsStale: boolean; } export class StockPositionResourceDto { @ApiProperty({ type: StockPositionDto, nullable: true }) declare data: null | StockPositionDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockSearchResourceDto { // eslint-disable-next-line @darraghor/nestjs-typed/api-property-returning-array-should-set-array -- Nullable array union requires isArray in the explicit wire schema. @ApiProperty({ type: StockSearchResultDto, isArray: true, nullable: true }) declare data: null | StockSearchResultDto[]; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockResourceDto { @ApiProperty({ type: StockDto, nullable: true }) declare data: null | StockDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockQuoteResourceDto { @ApiProperty({ type: StockQuoteDto, nullable: true }) declare data: null | StockQuoteDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockCompanyResourceDto { @ApiProperty({ type: StockCompanyDto, nullable: true }) declare data: null | StockCompanyDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockCandlesResourceDto { @ApiProperty({ type: StockCandlesDto, nullable: true }) declare data: null | StockCandlesDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; } export class StockDividendsResourceDto { @ApiProperty({ type: StockDividendsDto, nullable: true }) declare data: null | StockDividendsDto; @ApiProperty({ type: InvestmentResourceMetaDto }) declare meta: InvestmentResourceMetaDto; }