Files
backend/src/modules/investments/instruments/stocks/presentation/stock.dto.ts
T
codex-bot 2ad3517d87
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 12s
Backend verify / verify (push) Failing after 5m50s
fix: restore screener OpenAPI constraints (WealthLedger/backend#70) (#95)
2026-10-15 06:00:39 +03:00

351 lines
12 KiB
TypeScript

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;
}