linxus fs fixes

This commit is contained in:
Boki 2025-06-09 22:55:51 -04:00
parent ac23b70146
commit 0b7846fe67
292 changed files with 41947 additions and 41947 deletions

View file

@ -1,48 +1,48 @@
/* Dashboard specific styles */
.portfolio-card {
background-color: white !important;
border-radius: 0.5rem;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
border: 1px solid #f3f4f6;
padding: 1.5rem;
}
.metric-value {
font-size: 1.5rem;
font-weight: 700;
}
.metric-label {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.metric-change-positive {
color: #16a34a;
font-weight: 500;
}
.metric-change-negative {
color: #dc2626;
font-weight: 500;
}
.status-chip-active {
background-color: #dcfce7;
color: #166534;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
display: inline-block;
}
.status-chip-medium {
background-color: #dbeafe;
color: #1e40af;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
/* Dashboard specific styles */
.portfolio-card {
background-color: white !important;
border-radius: 0.5rem;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
border: 1px solid #f3f4f6;
padding: 1.5rem;
}
.metric-value {
font-size: 1.5rem;
font-weight: 700;
}
.metric-label {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.metric-change-positive {
color: #16a34a;
font-weight: 500;
}
.metric-change-negative {
color: #dc2626;
font-weight: 500;
}
.status-chip-active {
background-color: #dcfce7;
color: #166534;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
display: inline-block;
}
.status-chip-medium {
background-color: #dbeafe;
color: #1e40af;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}

View file

@ -1,154 +1,154 @@
<!-- Portfolio Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<!-- Total Portfolio Value -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Portfolio Value</p>
<p class="metric-value text-gray-900">
${{ portfolioValue().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance_wallet</mat-icon>
</div>
</mat-card>
<!-- Day Change -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Day Change</p>
<p class="metric-value"
[class.metric-change-positive]="dayChange() > 0"
[class.metric-change-negative]="dayChange() < 0">
${{ dayChange().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
</p>
<p class="text-sm font-medium"
[class.metric-change-positive]="dayChangePercent() > 0"
[class.metric-change-negative]="dayChangePercent() < 0">
{{ dayChangePercent() > 0 ? '+' : '' }}{{ dayChangePercent().toFixed(2) }}%
</p>
</div>
<mat-icon
class="text-3xl"
[class.metric-change-positive]="dayChange() > 0"
[class.metric-change-negative]="dayChange() < 0">
{{ dayChange() > 0 ? 'trending_up' : 'trending_down' }}
</mat-icon>
</div>
</mat-card>
<!-- Active Strategies -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Active Strategies</p>
<p class="metric-value text-gray-900">3</p>
<span class="status-chip-active">Running</span>
</div>
<mat-icon class="text-purple-600 text-3xl">psychology</mat-icon>
</div>
</mat-card>
<!-- Risk Level -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Risk Level</p>
<p class="metric-value text-green-600">Low</p>
<span class="status-chip-medium">Moderate</span>
</div>
<mat-icon class="text-green-600 text-3xl">security</mat-icon>
</div>
</mat-card>
</div>
<!-- Market Data Table -->
<mat-card class="p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Market Watchlist</h3>
<button mat-raised-button color="primary">
<mat-icon>refresh</mat-icon>
Refresh
</button>
</div>
<div class="overflow-x-auto">
<table mat-table [dataSource]="marketData()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let stock" class="font-semibold text-gray-900">{{ stock.symbol }}</td>
</ng-container>
<!-- Price Column -->
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Price</th>
<td mat-cell *matCellDef="let stock" class="text-right font-medium text-gray-900">
${{ stock.price.toFixed(2) }}
</td>
</ng-container>
<!-- Change Column -->
<ng-container matColumnDef="change">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.change > 0"
[class.text-red-600]="stock.change < 0">
{{ stock.change > 0 ? '+' : '' }}${{ stock.change.toFixed(2) }}
</td>
</ng-container>
<!-- Change Percent Column -->
<ng-container matColumnDef="changePercent">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change %</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.changePercent > 0"
[class.text-red-600]="stock.changePercent < 0">
{{ stock.changePercent > 0 ? '+' : '' }}{{ stock.changePercent.toFixed(2) }}%
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</mat-card>
<!-- Tabs for Additional Content -->
<mat-tab-group>
<mat-tab label="Chart">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">show_chart</mat-icon>
<p class="mb-4">Chart visualization will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Orders">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">receipt_long</mat-icon>
<p class="mb-4">Order history and management will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Analytics">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">analytics</mat-icon>
<p class="mb-4">Advanced analytics and performance metrics will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>
<!-- Portfolio Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<!-- Total Portfolio Value -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Portfolio Value</p>
<p class="metric-value text-gray-900">
${{ portfolioValue().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance_wallet</mat-icon>
</div>
</mat-card>
<!-- Day Change -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Day Change</p>
<p class="metric-value"
[class.metric-change-positive]="dayChange() > 0"
[class.metric-change-negative]="dayChange() < 0">
${{ dayChange().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
</p>
<p class="text-sm font-medium"
[class.metric-change-positive]="dayChangePercent() > 0"
[class.metric-change-negative]="dayChangePercent() < 0">
{{ dayChangePercent() > 0 ? '+' : '' }}{{ dayChangePercent().toFixed(2) }}%
</p>
</div>
<mat-icon
class="text-3xl"
[class.metric-change-positive]="dayChange() > 0"
[class.metric-change-negative]="dayChange() < 0">
{{ dayChange() > 0 ? 'trending_up' : 'trending_down' }}
</mat-icon>
</div>
</mat-card>
<!-- Active Strategies -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Active Strategies</p>
<p class="metric-value text-gray-900">3</p>
<span class="status-chip-active">Running</span>
</div>
<mat-icon class="text-purple-600 text-3xl">psychology</mat-icon>
</div>
</mat-card>
<!-- Risk Level -->
<mat-card class="portfolio-card">
<div class="flex items-center justify-between">
<div>
<p class="metric-label">Risk Level</p>
<p class="metric-value text-green-600">Low</p>
<span class="status-chip-medium">Moderate</span>
</div>
<mat-icon class="text-green-600 text-3xl">security</mat-icon>
</div>
</mat-card>
</div>
<!-- Market Data Table -->
<mat-card class="p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Market Watchlist</h3>
<button mat-raised-button color="primary">
<mat-icon>refresh</mat-icon>
Refresh
</button>
</div>
<div class="overflow-x-auto">
<table mat-table [dataSource]="marketData()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let stock" class="font-semibold text-gray-900">{{ stock.symbol }}</td>
</ng-container>
<!-- Price Column -->
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Price</th>
<td mat-cell *matCellDef="let stock" class="text-right font-medium text-gray-900">
${{ stock.price.toFixed(2) }}
</td>
</ng-container>
<!-- Change Column -->
<ng-container matColumnDef="change">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.change > 0"
[class.text-red-600]="stock.change < 0">
{{ stock.change > 0 ? '+' : '' }}${{ stock.change.toFixed(2) }}
</td>
</ng-container>
<!-- Change Percent Column -->
<ng-container matColumnDef="changePercent">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change %</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.changePercent > 0"
[class.text-red-600]="stock.changePercent < 0">
{{ stock.changePercent > 0 ? '+' : '' }}{{ stock.changePercent.toFixed(2) }}%
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</mat-card>
<!-- Tabs for Additional Content -->
<mat-tab-group>
<mat-tab label="Chart">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">show_chart</mat-icon>
<p class="mb-4">Chart visualization will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Orders">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">receipt_long</mat-icon>
<p class="mb-4">Order history and management will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Analytics">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">analytics</mat-icon>
<p class="mb-4">Advanced analytics and performance metrics will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>

View file

@ -1,44 +1,44 @@
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
export interface MarketDataItem {
symbol: string;
price: number;
change: number;
changePercent: number;
}
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatTabsModule,
MatButtonModule,
MatIconModule,
MatTableModule
],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.css'
})
export class DashboardComponent {
// Mock data for the dashboard
protected marketData = signal<MarketDataItem[]>([
{ symbol: 'AAPL', price: 192.53, change: 2.41, changePercent: 1.27 },
{ symbol: 'GOOGL', price: 138.21, change: -1.82, changePercent: -1.30 },
{ symbol: 'MSFT', price: 378.85, change: 4.12, changePercent: 1.10 },
{ symbol: 'TSLA', price: 248.42, change: -3.21, changePercent: -1.28 },
]);
protected portfolioValue = signal(125420.50);
protected dayChange = signal(2341.20);
protected dayChangePercent = signal(1.90);
protected displayedColumns: string[] = ['symbol', 'price', 'change', 'changePercent'];
}
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
export interface MarketDataItem {
symbol: string;
price: number;
change: number;
changePercent: number;
}
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatTabsModule,
MatButtonModule,
MatIconModule,
MatTableModule
],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.css'
})
export class DashboardComponent {
// Mock data for the dashboard
protected marketData = signal<MarketDataItem[]>([
{ symbol: 'AAPL', price: 192.53, change: 2.41, changePercent: 1.27 },
{ symbol: 'GOOGL', price: 138.21, change: -1.82, changePercent: -1.30 },
{ symbol: 'MSFT', price: 378.85, change: 4.12, changePercent: 1.10 },
{ symbol: 'TSLA', price: 248.42, change: -3.21, changePercent: -1.28 },
]);
protected portfolioValue = signal(125420.50);
protected dayChange = signal(2341.20);
protected dayChangePercent = signal(1.90);
protected displayedColumns: string[] = ['symbol', 'price', 'change', 'changePercent'];
}

View file

@ -1 +1 @@
/* Market Data specific styles */
/* Market Data specific styles */

View file

@ -1,172 +1,172 @@
<div class="space-y-6"> <!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Market Data</h1>
<p class="text-gray-600 mt-1">Real-time market information and analytics</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Market Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Market Status</p>
<p class="text-lg font-semibold text-green-600">Open</p>
</div>
<mat-icon class="text-green-600 text-3xl">schedule</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Active Instruments</p>
<p class="text-lg font-semibold text-gray-900">{{ marketData().length }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">trending_up</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between"> <div>
<p class="text-sm text-gray-600">Last Update</p>
<p class="text-lg font-semibold text-gray-900">{{ currentTime() }}</p>
</div>
<mat-icon class="text-purple-600 text-3xl">access_time</mat-icon>
</div>
</mat-card>
</div>
<!-- Market Data Table -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Live Market Data</h3>
<div class="flex gap-2">
<button mat-button>
<mat-icon>filter_list</mat-icon>
Filter
</button>
<button mat-button>
<mat-icon>file_download</mat-icon>
Export
</button>
</div>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading market data...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="marketData()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let stock" class="font-semibold text-gray-900">{{ stock.symbol }}</td>
</ng-container>
<!-- Price Column -->
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Price</th>
<td mat-cell *matCellDef="let stock" class="text-right font-medium text-gray-900">
${{ stock.price.toFixed(2) }}
</td>
</ng-container>
<!-- Change Column -->
<ng-container matColumnDef="change">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.change > 0"
[class.text-red-600]="stock.change < 0">
{{ stock.change > 0 ? '+' : '' }}${{ stock.change.toFixed(2) }}
</td>
</ng-container>
<!-- Change Percent Column -->
<ng-container matColumnDef="changePercent">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change %</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.changePercent > 0"
[class.text-red-600]="stock.changePercent < 0">
{{ stock.changePercent > 0 ? '+' : '' }}{{ stock.changePercent.toFixed(2) }}%
</td>
</ng-container>
<!-- Volume Column -->
<ng-container matColumnDef="volume">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Volume</th>
<td mat-cell *matCellDef="let stock" class="text-right text-gray-600">
{{ stock.volume.toLocaleString() }}
</td>
</ng-container>
<!-- Market Cap Column -->
<ng-container matColumnDef="marketCap">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Market Cap</th>
<td mat-cell *matCellDef="let stock" class="text-right text-gray-600">
${{ stock.marketCap }}
</td>
</ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
<!-- Market Analytics Tabs -->
<mat-tab-group>
<mat-tab label="Technical Analysis">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">bar_chart</mat-icon>
<p class="mb-4">Technical analysis charts and indicators will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Market Trends">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">timeline</mat-icon>
<p class="mb-4">Market trends and sector analysis will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="News & Events">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">article</mat-icon>
<p class="mb-4">Market news and economic events will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>
</div>
<div class="space-y-6"> <!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Market Data</h1>
<p class="text-gray-600 mt-1">Real-time market information and analytics</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Market Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Market Status</p>
<p class="text-lg font-semibold text-green-600">Open</p>
</div>
<mat-icon class="text-green-600 text-3xl">schedule</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Active Instruments</p>
<p class="text-lg font-semibold text-gray-900">{{ marketData().length }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">trending_up</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between"> <div>
<p class="text-sm text-gray-600">Last Update</p>
<p class="text-lg font-semibold text-gray-900">{{ currentTime() }}</p>
</div>
<mat-icon class="text-purple-600 text-3xl">access_time</mat-icon>
</div>
</mat-card>
</div>
<!-- Market Data Table -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Live Market Data</h3>
<div class="flex gap-2">
<button mat-button>
<mat-icon>filter_list</mat-icon>
Filter
</button>
<button mat-button>
<mat-icon>file_download</mat-icon>
Export
</button>
</div>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading market data...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="marketData()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let stock" class="font-semibold text-gray-900">{{ stock.symbol }}</td>
</ng-container>
<!-- Price Column -->
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Price</th>
<td mat-cell *matCellDef="let stock" class="text-right font-medium text-gray-900">
${{ stock.price.toFixed(2) }}
</td>
</ng-container>
<!-- Change Column -->
<ng-container matColumnDef="change">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.change > 0"
[class.text-red-600]="stock.change < 0">
{{ stock.change > 0 ? '+' : '' }}${{ stock.change.toFixed(2) }}
</td>
</ng-container>
<!-- Change Percent Column -->
<ng-container matColumnDef="changePercent">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Change %</th>
<td mat-cell *matCellDef="let stock"
class="text-right font-medium"
[class.text-green-600]="stock.changePercent > 0"
[class.text-red-600]="stock.changePercent < 0">
{{ stock.changePercent > 0 ? '+' : '' }}{{ stock.changePercent.toFixed(2) }}%
</td>
</ng-container>
<!-- Volume Column -->
<ng-container matColumnDef="volume">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Volume</th>
<td mat-cell *matCellDef="let stock" class="text-right text-gray-600">
{{ stock.volume.toLocaleString() }}
</td>
</ng-container>
<!-- Market Cap Column -->
<ng-container matColumnDef="marketCap">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Market Cap</th>
<td mat-cell *matCellDef="let stock" class="text-right text-gray-600">
${{ stock.marketCap }}
</td>
</ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
<!-- Market Analytics Tabs -->
<mat-tab-group>
<mat-tab label="Technical Analysis">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">bar_chart</mat-icon>
<p class="mb-4">Technical analysis charts and indicators will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Market Trends">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">timeline</mat-icon>
<p class="mb-4">Market trends and sector analysis will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="News & Events">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">article</mat-icon>
<p class="mb-4">Market news and economic events will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>
</div>

View file

@ -1,198 +1,198 @@
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { ApiService } from '../../services/api.service';
import { WebSocketService } from '../../services/websocket.service';
import { interval, Subscription } from 'rxjs';
export interface ExtendedMarketData {
symbol: string;
price: number;
change: number;
changePercent: number;
volume: number;
marketCap: string;
high52Week: number;
low52Week: number;
}
@Component({
selector: 'app-market-data',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatTableModule,
MatTabsModule,
MatProgressSpinnerModule,
MatSnackBarModule
],
templateUrl: './market-data.component.html',
styleUrl: './market-data.component.css'
})
export class MarketDataComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private webSocketService = inject(WebSocketService);
private snackBar = inject(MatSnackBar);
private subscriptions: Subscription[] = [];
protected marketData = signal<ExtendedMarketData[]>([]);
protected currentTime = signal<string>(new Date().toLocaleTimeString());
protected isLoading = signal<boolean>(true);
protected error = signal<string | null>(null);
protected displayedColumns: string[] = ['symbol', 'price', 'change', 'changePercent', 'volume', 'marketCap'];
ngOnInit() {
// Update time every second
const timeSubscription = interval(1000).subscribe(() => {
this.currentTime.set(new Date().toLocaleTimeString());
});
this.subscriptions.push(timeSubscription);
// Load initial market data
this.loadMarketData();
// Subscribe to real-time market data updates
const wsSubscription = this.webSocketService.getMarketDataUpdates().subscribe({
next: (update) => {
this.updateMarketData(update);
},
error: (err) => {
console.error('WebSocket market data error:', err);
}
});
this.subscriptions.push(wsSubscription);
// Fallback: Refresh market data every 30 seconds if WebSocket fails
const dataSubscription = interval(30000).subscribe(() => {
if (!this.webSocketService.isConnected()) {
this.loadMarketData();
}
});
this.subscriptions.push(dataSubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadMarketData() {
this.apiService.getMarketData().subscribe({
next: (response) => {
// Convert MarketData to ExtendedMarketData with mock extended properties
const extendedData: ExtendedMarketData[] = response.data.map(item => ({
...item,
marketCap: this.getMockMarketCap(item.symbol),
high52Week: item.price * 1.3, // Mock 52-week high (30% above current)
low52Week: item.price * 0.7 // Mock 52-week low (30% below current)
}));
this.marketData.set(extendedData);
this.isLoading.set(false);
this.error.set(null);
},
error: (err) => {
console.error('Failed to load market data:', err);
this.error.set('Failed to load market data');
this.isLoading.set(false);
this.snackBar.open('Failed to load market data', 'Dismiss', { duration: 5000 });
// Use mock data as fallback
this.marketData.set(this.getMockData());
}
});
}
private getMockMarketCap(symbol: string): string {
const marketCaps: { [key: string]: string } = {
'AAPL': '2.98T',
'GOOGL': '1.78T',
'MSFT': '3.08T',
'TSLA': '789.2B',
'AMZN': '1.59T'
};
return marketCaps[symbol] || '1.00T';
}
private getMockData(): ExtendedMarketData[] {
return [
{
symbol: 'AAPL',
price: 192.53,
change: 2.41,
changePercent: 1.27,
volume: 45230000,
marketCap: '2.98T',
high52Week: 199.62,
low52Week: 164.08
},
{
symbol: 'GOOGL',
price: 2847.56,
change: -12.34,
changePercent: -0.43,
volume: 12450000,
marketCap: '1.78T',
high52Week: 3030.93,
low52Week: 2193.62
},
{
symbol: 'MSFT',
price: 415.26,
change: 8.73,
changePercent: 2.15,
volume: 23180000,
marketCap: '3.08T',
high52Week: 468.35,
low52Week: 309.45
},
{
symbol: 'TSLA',
price: 248.50,
change: -5.21,
changePercent: -2.05,
volume: 89760000,
marketCap: '789.2B',
high52Week: 299.29,
low52Week: 152.37
},
{
symbol: 'AMZN',
price: 152.74,
change: 3.18,
changePercent: 2.12,
volume: 34520000,
marketCap: '1.59T',
high52Week: 170.17,
low52Week: 118.35
}
];
}
refreshData() {
this.isLoading.set(true);
this.loadMarketData();
}
private updateMarketData(update: any) {
const currentData = this.marketData();
const updatedData = currentData.map(item => {
if (item.symbol === update.symbol) {
return {
...item,
price: update.price,
change: update.change,
changePercent: update.changePercent,
volume: update.volume
};
}
return item;
});
this.marketData.set(updatedData);
}
}
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { ApiService } from '../../services/api.service';
import { WebSocketService } from '../../services/websocket.service';
import { interval, Subscription } from 'rxjs';
export interface ExtendedMarketData {
symbol: string;
price: number;
change: number;
changePercent: number;
volume: number;
marketCap: string;
high52Week: number;
low52Week: number;
}
@Component({
selector: 'app-market-data',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatTableModule,
MatTabsModule,
MatProgressSpinnerModule,
MatSnackBarModule
],
templateUrl: './market-data.component.html',
styleUrl: './market-data.component.css'
})
export class MarketDataComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private webSocketService = inject(WebSocketService);
private snackBar = inject(MatSnackBar);
private subscriptions: Subscription[] = [];
protected marketData = signal<ExtendedMarketData[]>([]);
protected currentTime = signal<string>(new Date().toLocaleTimeString());
protected isLoading = signal<boolean>(true);
protected error = signal<string | null>(null);
protected displayedColumns: string[] = ['symbol', 'price', 'change', 'changePercent', 'volume', 'marketCap'];
ngOnInit() {
// Update time every second
const timeSubscription = interval(1000).subscribe(() => {
this.currentTime.set(new Date().toLocaleTimeString());
});
this.subscriptions.push(timeSubscription);
// Load initial market data
this.loadMarketData();
// Subscribe to real-time market data updates
const wsSubscription = this.webSocketService.getMarketDataUpdates().subscribe({
next: (update) => {
this.updateMarketData(update);
},
error: (err) => {
console.error('WebSocket market data error:', err);
}
});
this.subscriptions.push(wsSubscription);
// Fallback: Refresh market data every 30 seconds if WebSocket fails
const dataSubscription = interval(30000).subscribe(() => {
if (!this.webSocketService.isConnected()) {
this.loadMarketData();
}
});
this.subscriptions.push(dataSubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadMarketData() {
this.apiService.getMarketData().subscribe({
next: (response) => {
// Convert MarketData to ExtendedMarketData with mock extended properties
const extendedData: ExtendedMarketData[] = response.data.map(item => ({
...item,
marketCap: this.getMockMarketCap(item.symbol),
high52Week: item.price * 1.3, // Mock 52-week high (30% above current)
low52Week: item.price * 0.7 // Mock 52-week low (30% below current)
}));
this.marketData.set(extendedData);
this.isLoading.set(false);
this.error.set(null);
},
error: (err) => {
console.error('Failed to load market data:', err);
this.error.set('Failed to load market data');
this.isLoading.set(false);
this.snackBar.open('Failed to load market data', 'Dismiss', { duration: 5000 });
// Use mock data as fallback
this.marketData.set(this.getMockData());
}
});
}
private getMockMarketCap(symbol: string): string {
const marketCaps: { [key: string]: string } = {
'AAPL': '2.98T',
'GOOGL': '1.78T',
'MSFT': '3.08T',
'TSLA': '789.2B',
'AMZN': '1.59T'
};
return marketCaps[symbol] || '1.00T';
}
private getMockData(): ExtendedMarketData[] {
return [
{
symbol: 'AAPL',
price: 192.53,
change: 2.41,
changePercent: 1.27,
volume: 45230000,
marketCap: '2.98T',
high52Week: 199.62,
low52Week: 164.08
},
{
symbol: 'GOOGL',
price: 2847.56,
change: -12.34,
changePercent: -0.43,
volume: 12450000,
marketCap: '1.78T',
high52Week: 3030.93,
low52Week: 2193.62
},
{
symbol: 'MSFT',
price: 415.26,
change: 8.73,
changePercent: 2.15,
volume: 23180000,
marketCap: '3.08T',
high52Week: 468.35,
low52Week: 309.45
},
{
symbol: 'TSLA',
price: 248.50,
change: -5.21,
changePercent: -2.05,
volume: 89760000,
marketCap: '789.2B',
high52Week: 299.29,
low52Week: 152.37
},
{
symbol: 'AMZN',
price: 152.74,
change: 3.18,
changePercent: 2.12,
volume: 34520000,
marketCap: '1.59T',
high52Week: 170.17,
low52Week: 118.35
}
];
}
refreshData() {
this.isLoading.set(true);
this.loadMarketData();
}
private updateMarketData(update: any) {
const currentData = this.marketData();
const updatedData = currentData.map(item => {
if (item.symbol === update.symbol) {
return {
...item,
price: update.price,
change: update.change,
changePercent: update.changePercent,
volume: update.volume
};
}
return item;
});
this.marketData.set(updatedData);
}
}

View file

@ -1 +1 @@
/* Portfolio specific styles */
/* Portfolio specific styles */

View file

@ -1,203 +1,203 @@
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Portfolio</h1>
<p class="text-gray-600 mt-1">Manage and monitor your investment portfolio</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Portfolio Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Total Value</p>
<p class="text-lg font-semibold text-gray-900">${{ portfolioSummary().totalValue.toLocaleString() }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance_wallet</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Total P&L</p>
<p class="text-lg font-semibold" [class]="getPnLColor(portfolioSummary().totalPnL)">
{{ portfolioSummary().totalPnL > 0 ? '+' : '' }}${{ portfolioSummary().totalPnL.toLocaleString() }}
({{ portfolioSummary().totalPnLPercent.toFixed(2) }}%)
</p>
</div>
<mat-icon class="text-green-600 text-3xl" *ngIf="portfolioSummary().totalPnL >= 0">trending_up</mat-icon>
<mat-icon class="text-red-600 text-3xl" *ngIf="portfolioSummary().totalPnL < 0">trending_down</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Day Change</p>
<p class="text-lg font-semibold" [class]="getPnLColor(portfolioSummary().dayChange)">
{{ portfolioSummary().dayChange > 0 ? '+' : '' }}${{ portfolioSummary().dayChange.toLocaleString() }}
({{ portfolioSummary().dayChangePercent.toFixed(2) }}%)
</p>
</div>
<mat-icon class="text-purple-600 text-3xl">today</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Cash Available</p>
<p class="text-lg font-semibold text-gray-900">${{ portfolioSummary().cash.toLocaleString() }}</p>
</div>
<mat-icon class="text-yellow-600 text-3xl">attach_money</mat-icon>
</div>
</mat-card>
</div>
<!-- Portfolio Tabs -->
<mat-tab-group>
<mat-tab label="Positions">
<div class="p-6">
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Current Positions</h3>
<div class="flex gap-2">
<button mat-button>
<mat-icon>add</mat-icon>
Add Position
</button>
<button mat-button>
<mat-icon>file_download</mat-icon>
Export
</button>
</div>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading portfolio...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else if (positions().length === 0) {
<div class="text-center py-8 text-gray-500">
<mat-icon class="text-4xl">account_balance_wallet</mat-icon>
<p class="mt-2">No positions found</p>
<button mat-button color="primary" class="mt-2">
<mat-icon>add</mat-icon>
Add Your First Position
</button>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="positions()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let position" class="font-semibold text-gray-900">{{ position.symbol }}</td>
</ng-container>
<!-- Quantity Column -->
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Quantity</th>
<td mat-cell *matCellDef="let position" class="text-right text-gray-600">
{{ position.quantity.toLocaleString() }}
</td>
</ng-container>
<!-- Average Price Column -->
<ng-container matColumnDef="avgPrice">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Avg Price</th>
<td mat-cell *matCellDef="let position" class="text-right text-gray-600">
${{ position.avgPrice.toFixed(2) }}
</td>
</ng-container>
<!-- Current Price Column -->
<ng-container matColumnDef="currentPrice">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Current Price</th>
<td mat-cell *matCellDef="let position" class="text-right font-medium text-gray-900">
${{ position.currentPrice.toFixed(2) }}
</td>
</ng-container>
<!-- Market Value Column -->
<ng-container matColumnDef="marketValue">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Market Value</th>
<td mat-cell *matCellDef="let position" class="text-right font-medium text-gray-900">
${{ position.marketValue.toLocaleString() }}
</td>
</ng-container>
<!-- Unrealized P&L Column -->
<ng-container matColumnDef="unrealizedPnL">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Unrealized P&L</th>
<td mat-cell *matCellDef="let position"
class="text-right font-medium"
[class]="getPnLColor(position.unrealizedPnL)">
{{ position.unrealizedPnL > 0 ? '+' : '' }}${{ position.unrealizedPnL.toLocaleString() }}
({{ position.unrealizedPnLPercent.toFixed(2) }}%)
</td>
</ng-container>
<!-- Day Change Column -->
<ng-container matColumnDef="dayChange">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Day Change</th>
<td mat-cell *matCellDef="let position"
class="text-right font-medium"
[class]="getPnLColor(position.dayChange)">
{{ position.dayChange > 0 ? '+' : '' }}${{ position.dayChange.toFixed(2) }}
({{ position.dayChangePercent.toFixed(2) }}%)
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
</div>
</mat-tab>
<mat-tab label="Performance">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">trending_up</mat-icon>
<p class="mb-4">Performance charts and analytics will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Orders">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">receipt</mat-icon>
<p class="mb-4">Order history and management will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>
</div>
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Portfolio</h1>
<p class="text-gray-600 mt-1">Manage and monitor your investment portfolio</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Portfolio Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Total Value</p>
<p class="text-lg font-semibold text-gray-900">${{ portfolioSummary().totalValue.toLocaleString() }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance_wallet</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Total P&L</p>
<p class="text-lg font-semibold" [class]="getPnLColor(portfolioSummary().totalPnL)">
{{ portfolioSummary().totalPnL > 0 ? '+' : '' }}${{ portfolioSummary().totalPnL.toLocaleString() }}
({{ portfolioSummary().totalPnLPercent.toFixed(2) }}%)
</p>
</div>
<mat-icon class="text-green-600 text-3xl" *ngIf="portfolioSummary().totalPnL >= 0">trending_up</mat-icon>
<mat-icon class="text-red-600 text-3xl" *ngIf="portfolioSummary().totalPnL < 0">trending_down</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Day Change</p>
<p class="text-lg font-semibold" [class]="getPnLColor(portfolioSummary().dayChange)">
{{ portfolioSummary().dayChange > 0 ? '+' : '' }}${{ portfolioSummary().dayChange.toLocaleString() }}
({{ portfolioSummary().dayChangePercent.toFixed(2) }}%)
</p>
</div>
<mat-icon class="text-purple-600 text-3xl">today</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Cash Available</p>
<p class="text-lg font-semibold text-gray-900">${{ portfolioSummary().cash.toLocaleString() }}</p>
</div>
<mat-icon class="text-yellow-600 text-3xl">attach_money</mat-icon>
</div>
</mat-card>
</div>
<!-- Portfolio Tabs -->
<mat-tab-group>
<mat-tab label="Positions">
<div class="p-6">
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Current Positions</h3>
<div class="flex gap-2">
<button mat-button>
<mat-icon>add</mat-icon>
Add Position
</button>
<button mat-button>
<mat-icon>file_download</mat-icon>
Export
</button>
</div>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading portfolio...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else if (positions().length === 0) {
<div class="text-center py-8 text-gray-500">
<mat-icon class="text-4xl">account_balance_wallet</mat-icon>
<p class="mt-2">No positions found</p>
<button mat-button color="primary" class="mt-2">
<mat-icon>add</mat-icon>
Add Your First Position
</button>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="positions()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let position" class="font-semibold text-gray-900">{{ position.symbol }}</td>
</ng-container>
<!-- Quantity Column -->
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Quantity</th>
<td mat-cell *matCellDef="let position" class="text-right text-gray-600">
{{ position.quantity.toLocaleString() }}
</td>
</ng-container>
<!-- Average Price Column -->
<ng-container matColumnDef="avgPrice">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Avg Price</th>
<td mat-cell *matCellDef="let position" class="text-right text-gray-600">
${{ position.avgPrice.toFixed(2) }}
</td>
</ng-container>
<!-- Current Price Column -->
<ng-container matColumnDef="currentPrice">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Current Price</th>
<td mat-cell *matCellDef="let position" class="text-right font-medium text-gray-900">
${{ position.currentPrice.toFixed(2) }}
</td>
</ng-container>
<!-- Market Value Column -->
<ng-container matColumnDef="marketValue">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Market Value</th>
<td mat-cell *matCellDef="let position" class="text-right font-medium text-gray-900">
${{ position.marketValue.toLocaleString() }}
</td>
</ng-container>
<!-- Unrealized P&L Column -->
<ng-container matColumnDef="unrealizedPnL">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Unrealized P&L</th>
<td mat-cell *matCellDef="let position"
class="text-right font-medium"
[class]="getPnLColor(position.unrealizedPnL)">
{{ position.unrealizedPnL > 0 ? '+' : '' }}${{ position.unrealizedPnL.toLocaleString() }}
({{ position.unrealizedPnLPercent.toFixed(2) }}%)
</td>
</ng-container>
<!-- Day Change Column -->
<ng-container matColumnDef="dayChange">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Day Change</th>
<td mat-cell *matCellDef="let position"
class="text-right font-medium"
[class]="getPnLColor(position.dayChange)">
{{ position.dayChange > 0 ? '+' : '' }}${{ position.dayChange.toFixed(2) }}
({{ position.dayChangePercent.toFixed(2) }}%)
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
</div>
</mat-tab>
<mat-tab label="Performance">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">trending_up</mat-icon>
<p class="mb-4">Performance charts and analytics will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
<mat-tab label="Orders">
<div class="p-6">
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">receipt</mat-icon>
<p class="mb-4">Order history and management will be implemented here</p>
</div>
</mat-card>
</div>
</mat-tab>
</mat-tab-group>
</div>

View file

@ -1,159 +1,159 @@
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { ApiService } from '../../services/api.service';
import { interval, Subscription } from 'rxjs';
export interface Position {
symbol: string;
quantity: number;
avgPrice: number;
currentPrice: number;
marketValue: number;
unrealizedPnL: number;
unrealizedPnLPercent: number;
dayChange: number;
dayChangePercent: number;
}
export interface PortfolioSummary {
totalValue: number;
totalCost: number;
totalPnL: number;
totalPnLPercent: number;
dayChange: number;
dayChangePercent: number;
cash: number;
positionsCount: number;
}
@Component({
selector: 'app-portfolio',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatTabsModule
],
templateUrl: './portfolio.component.html',
styleUrl: './portfolio.component.css'
})
export class PortfolioComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private subscriptions: Subscription[] = [];
protected portfolioSummary = signal<PortfolioSummary>({
totalValue: 0,
totalCost: 0,
totalPnL: 0,
totalPnLPercent: 0,
dayChange: 0,
dayChangePercent: 0,
cash: 0,
positionsCount: 0
});
protected positions = signal<Position[]>([]);
protected isLoading = signal<boolean>(true);
protected error = signal<string | null>(null);
protected displayedColumns = ['symbol', 'quantity', 'avgPrice', 'currentPrice', 'marketValue', 'unrealizedPnL', 'dayChange'];
ngOnInit() {
this.loadPortfolioData();
// Refresh portfolio data every 30 seconds
const portfolioSubscription = interval(30000).subscribe(() => {
this.loadPortfolioData();
});
this.subscriptions.push(portfolioSubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadPortfolioData() {
// Since we don't have a portfolio endpoint yet, let's create mock data
// In a real implementation, this would call this.apiService.getPortfolio()
setTimeout(() => {
const mockPositions: Position[] = [
{
symbol: 'AAPL',
quantity: 100,
avgPrice: 180.50,
currentPrice: 192.53,
marketValue: 19253,
unrealizedPnL: 1203,
unrealizedPnLPercent: 6.67,
dayChange: 241,
dayChangePercent: 1.27
},
{
symbol: 'MSFT',
quantity: 50,
avgPrice: 400.00,
currentPrice: 415.26,
marketValue: 20763,
unrealizedPnL: 763,
unrealizedPnLPercent: 3.82,
dayChange: 436.50,
dayChangePercent: 2.15
},
{
symbol: 'GOOGL',
quantity: 10,
avgPrice: 2900.00,
currentPrice: 2847.56,
marketValue: 28475.60,
unrealizedPnL: -524.40,
unrealizedPnLPercent: -1.81,
dayChange: -123.40,
dayChangePercent: -0.43
}
];
const summary: PortfolioSummary = {
totalValue: mockPositions.reduce((sum, pos) => sum + pos.marketValue, 0) + 25000, // + cash
totalCost: mockPositions.reduce((sum, pos) => sum + (pos.avgPrice * pos.quantity), 0),
totalPnL: mockPositions.reduce((sum, pos) => sum + pos.unrealizedPnL, 0),
totalPnLPercent: 0,
dayChange: mockPositions.reduce((sum, pos) => sum + pos.dayChange, 0),
dayChangePercent: 0,
cash: 25000,
positionsCount: mockPositions.length
};
summary.totalPnLPercent = (summary.totalPnL / summary.totalCost) * 100;
summary.dayChangePercent = (summary.dayChange / (summary.totalValue - summary.dayChange)) * 100;
this.positions.set(mockPositions);
this.portfolioSummary.set(summary);
this.isLoading.set(false);
this.error.set(null);
}, 1000);
}
refreshData() {
this.isLoading.set(true);
this.loadPortfolioData();
}
getPnLColor(value: number): string {
if (value > 0) return 'text-green-600';
if (value < 0) return 'text-red-600';
return 'text-gray-600';
}
}
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { ApiService } from '../../services/api.service';
import { interval, Subscription } from 'rxjs';
export interface Position {
symbol: string;
quantity: number;
avgPrice: number;
currentPrice: number;
marketValue: number;
unrealizedPnL: number;
unrealizedPnLPercent: number;
dayChange: number;
dayChangePercent: number;
}
export interface PortfolioSummary {
totalValue: number;
totalCost: number;
totalPnL: number;
totalPnLPercent: number;
dayChange: number;
dayChangePercent: number;
cash: number;
positionsCount: number;
}
@Component({
selector: 'app-portfolio',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatTabsModule
],
templateUrl: './portfolio.component.html',
styleUrl: './portfolio.component.css'
})
export class PortfolioComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private subscriptions: Subscription[] = [];
protected portfolioSummary = signal<PortfolioSummary>({
totalValue: 0,
totalCost: 0,
totalPnL: 0,
totalPnLPercent: 0,
dayChange: 0,
dayChangePercent: 0,
cash: 0,
positionsCount: 0
});
protected positions = signal<Position[]>([]);
protected isLoading = signal<boolean>(true);
protected error = signal<string | null>(null);
protected displayedColumns = ['symbol', 'quantity', 'avgPrice', 'currentPrice', 'marketValue', 'unrealizedPnL', 'dayChange'];
ngOnInit() {
this.loadPortfolioData();
// Refresh portfolio data every 30 seconds
const portfolioSubscription = interval(30000).subscribe(() => {
this.loadPortfolioData();
});
this.subscriptions.push(portfolioSubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadPortfolioData() {
// Since we don't have a portfolio endpoint yet, let's create mock data
// In a real implementation, this would call this.apiService.getPortfolio()
setTimeout(() => {
const mockPositions: Position[] = [
{
symbol: 'AAPL',
quantity: 100,
avgPrice: 180.50,
currentPrice: 192.53,
marketValue: 19253,
unrealizedPnL: 1203,
unrealizedPnLPercent: 6.67,
dayChange: 241,
dayChangePercent: 1.27
},
{
symbol: 'MSFT',
quantity: 50,
avgPrice: 400.00,
currentPrice: 415.26,
marketValue: 20763,
unrealizedPnL: 763,
unrealizedPnLPercent: 3.82,
dayChange: 436.50,
dayChangePercent: 2.15
},
{
symbol: 'GOOGL',
quantity: 10,
avgPrice: 2900.00,
currentPrice: 2847.56,
marketValue: 28475.60,
unrealizedPnL: -524.40,
unrealizedPnLPercent: -1.81,
dayChange: -123.40,
dayChangePercent: -0.43
}
];
const summary: PortfolioSummary = {
totalValue: mockPositions.reduce((sum, pos) => sum + pos.marketValue, 0) + 25000, // + cash
totalCost: mockPositions.reduce((sum, pos) => sum + (pos.avgPrice * pos.quantity), 0),
totalPnL: mockPositions.reduce((sum, pos) => sum + pos.unrealizedPnL, 0),
totalPnLPercent: 0,
dayChange: mockPositions.reduce((sum, pos) => sum + pos.dayChange, 0),
dayChangePercent: 0,
cash: 25000,
positionsCount: mockPositions.length
};
summary.totalPnLPercent = (summary.totalPnL / summary.totalCost) * 100;
summary.dayChangePercent = (summary.dayChange / (summary.totalValue - summary.dayChange)) * 100;
this.positions.set(mockPositions);
this.portfolioSummary.set(summary);
this.isLoading.set(false);
this.error.set(null);
}, 1000);
}
refreshData() {
this.isLoading.set(true);
this.loadPortfolioData();
}
getPnLColor(value: number): string {
if (value > 0) return 'text-green-600';
if (value < 0) return 'text-red-600';
return 'text-gray-600';
}
}

View file

@ -1 +1 @@
/* Risk Management specific styles */
/* Risk Management specific styles */

View file

@ -1,178 +1,178 @@
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Risk Management</h1>
<p class="text-gray-600 mt-1">Monitor and control trading risks and exposure</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Risk Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
@if (riskThresholds(); as thresholds) {
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Max Position Size</p>
<p class="text-lg font-semibold text-gray-900">${{ thresholds.maxPositionSize.toLocaleString() }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Max Daily Loss</p>
<p class="text-lg font-semibold text-red-600">${{ thresholds.maxDailyLoss.toLocaleString() }}</p>
</div>
<mat-icon class="text-red-600 text-3xl">trending_down</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Portfolio Risk Limit</p>
<p class="text-lg font-semibold text-yellow-600">{{ (thresholds.maxPortfolioRisk * 100).toFixed(1) }}%</p>
</div>
<mat-icon class="text-yellow-600 text-3xl">pie_chart</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Volatility Limit</p>
<p class="text-lg font-semibold text-purple-600">{{ (thresholds.volatilityLimit * 100).toFixed(1) }}%</p>
</div>
<mat-icon class="text-purple-600 text-3xl">show_chart</mat-icon>
</div>
</mat-card>
}
</div>
<!-- Risk Thresholds Configuration -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Risk Thresholds Configuration</h3>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading risk settings...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else {
<form [formGroup]="thresholdsForm" (ngSubmit)="saveThresholds()">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<mat-form-field appearance="outline">
<mat-label>Max Position Size ($)</mat-label>
<input matInput type="number" formControlName="maxPositionSize" placeholder="100000">
<mat-icon matSuffix>attach_money</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Daily Loss ($)</mat-label>
<input matInput type="number" formControlName="maxDailyLoss" placeholder="5000">
<mat-icon matSuffix>trending_down</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Portfolio Risk (0-1)</mat-label>
<input matInput type="number" step="0.01" formControlName="maxPortfolioRisk" placeholder="0.1">
<mat-icon matSuffix>pie_chart</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Volatility Limit (0-1)</mat-label>
<input matInput type="number" step="0.01" formControlName="volatilityLimit" placeholder="0.3">
<mat-icon matSuffix>show_chart</mat-icon>
</mat-form-field>
</div>
<div class="flex justify-end mt-4">
<button mat-raised-button color="primary" type="submit" [disabled]="thresholdsForm.invalid || isSaving()">
@if (isSaving()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Save Thresholds
</button>
</div>
</form>
}
</mat-card>
<!-- Risk History Table -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Recent Risk Evaluations</h3>
</div>
@if (riskHistory().length === 0) {
<div class="text-center py-8 text-gray-500">
<mat-icon class="text-4xl">history</mat-icon>
<p class="mt-2">No risk evaluations found</p>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="riskHistory()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let risk" class="font-semibold text-gray-900">{{ risk.symbol }}</td>
</ng-container>
<!-- Position Value Column -->
<ng-container matColumnDef="positionValue">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Position Value</th>
<td mat-cell *matCellDef="let risk" class="text-right font-medium text-gray-900">
${{ risk.positionValue.toLocaleString() }}
</td>
</ng-container>
<!-- Risk Level Column -->
<ng-container matColumnDef="riskLevel">
<th mat-header-cell *matHeaderCellDef class="text-center font-medium text-gray-900">Risk Level</th>
<td mat-cell *matCellDef="let risk" class="text-center">
<span class="px-2 py-1 rounded-full text-sm font-medium" [class]="getRiskLevelColor(risk.riskLevel)">
{{ risk.riskLevel }}
</span>
</td>
</ng-container>
<!-- Violations Column -->
<ng-container matColumnDef="violations">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Violations</th>
<td mat-cell *matCellDef="let risk" class="text-gray-600">
@if (risk.violations.length > 0) {
<span class="text-red-600">{{ risk.violations.join(', ') }}</span>
} @else {
<span class="text-green-600">None</span>
}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
</div>
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Risk Management</h1>
<p class="text-gray-600 mt-1">Monitor and control trading risks and exposure</p>
</div>
<button mat-raised-button color="primary" (click)="refreshData()" [disabled]="isLoading()">
<mat-icon>refresh</mat-icon>
@if (isLoading()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Refresh Data
</button>
</div>
<!-- Risk Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
@if (riskThresholds(); as thresholds) {
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Max Position Size</p>
<p class="text-lg font-semibold text-gray-900">${{ thresholds.maxPositionSize.toLocaleString() }}</p>
</div>
<mat-icon class="text-blue-600 text-3xl">account_balance</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Max Daily Loss</p>
<p class="text-lg font-semibold text-red-600">${{ thresholds.maxDailyLoss.toLocaleString() }}</p>
</div>
<mat-icon class="text-red-600 text-3xl">trending_down</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Portfolio Risk Limit</p>
<p class="text-lg font-semibold text-yellow-600">{{ (thresholds.maxPortfolioRisk * 100).toFixed(1) }}%</p>
</div>
<mat-icon class="text-yellow-600 text-3xl">pie_chart</mat-icon>
</div>
</mat-card>
<mat-card class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Volatility Limit</p>
<p class="text-lg font-semibold text-purple-600">{{ (thresholds.volatilityLimit * 100).toFixed(1) }}%</p>
</div>
<mat-icon class="text-purple-600 text-3xl">show_chart</mat-icon>
</div>
</mat-card>
}
</div>
<!-- Risk Thresholds Configuration -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Risk Thresholds Configuration</h3>
</div>
@if (isLoading()) {
<div class="flex justify-center items-center py-8">
<mat-spinner diameter="40"></mat-spinner>
<span class="ml-3 text-gray-600">Loading risk settings...</span>
</div>
} @else if (error()) {
<div class="text-center py-8">
<mat-icon class="text-red-500 text-4xl">error</mat-icon>
<p class="text-red-600 mt-2">{{ error() }}</p>
<button mat-button color="primary" (click)="refreshData()" class="mt-2">
<mat-icon>refresh</mat-icon>
Try Again
</button>
</div>
} @else {
<form [formGroup]="thresholdsForm" (ngSubmit)="saveThresholds()">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<mat-form-field appearance="outline">
<mat-label>Max Position Size ($)</mat-label>
<input matInput type="number" formControlName="maxPositionSize" placeholder="100000">
<mat-icon matSuffix>attach_money</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Daily Loss ($)</mat-label>
<input matInput type="number" formControlName="maxDailyLoss" placeholder="5000">
<mat-icon matSuffix>trending_down</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Portfolio Risk (0-1)</mat-label>
<input matInput type="number" step="0.01" formControlName="maxPortfolioRisk" placeholder="0.1">
<mat-icon matSuffix>pie_chart</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Volatility Limit (0-1)</mat-label>
<input matInput type="number" step="0.01" formControlName="volatilityLimit" placeholder="0.3">
<mat-icon matSuffix>show_chart</mat-icon>
</mat-form-field>
</div>
<div class="flex justify-end mt-4">
<button mat-raised-button color="primary" type="submit" [disabled]="thresholdsForm.invalid || isSaving()">
@if (isSaving()) {
<mat-spinner diameter="20" class="mr-2"></mat-spinner>
}
Save Thresholds
</button>
</div>
</form>
}
</mat-card>
<!-- Risk History Table -->
<mat-card class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">Recent Risk Evaluations</h3>
</div>
@if (riskHistory().length === 0) {
<div class="text-center py-8 text-gray-500">
<mat-icon class="text-4xl">history</mat-icon>
<p class="mt-2">No risk evaluations found</p>
</div>
} @else {
<div class="overflow-x-auto">
<table mat-table [dataSource]="riskHistory()" class="mat-elevation-z0 w-full">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Symbol</th>
<td mat-cell *matCellDef="let risk" class="font-semibold text-gray-900">{{ risk.symbol }}</td>
</ng-container>
<!-- Position Value Column -->
<ng-container matColumnDef="positionValue">
<th mat-header-cell *matHeaderCellDef class="text-right font-medium text-gray-900">Position Value</th>
<td mat-cell *matCellDef="let risk" class="text-right font-medium text-gray-900">
${{ risk.positionValue.toLocaleString() }}
</td>
</ng-container>
<!-- Risk Level Column -->
<ng-container matColumnDef="riskLevel">
<th mat-header-cell *matHeaderCellDef class="text-center font-medium text-gray-900">Risk Level</th>
<td mat-cell *matCellDef="let risk" class="text-center">
<span class="px-2 py-1 rounded-full text-sm font-medium" [class]="getRiskLevelColor(risk.riskLevel)">
{{ risk.riskLevel }}
</span>
</td>
</ng-container>
<!-- Violations Column -->
<ng-container matColumnDef="violations">
<th mat-header-cell *matHeaderCellDef class="font-medium text-gray-900">Violations</th>
<td mat-cell *matCellDef="let risk" class="text-gray-600">
@if (risk.violations.length > 0) {
<span class="text-red-600">{{ risk.violations.join(', ') }}</span>
} @else {
<span class="text-green-600">None</span>
}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
}
</mat-card>
</div>

View file

@ -1,135 +1,135 @@
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiService, RiskThresholds, RiskEvaluation } from '../../services/api.service';
import { interval, Subscription } from 'rxjs';
@Component({
selector: 'app-risk-management',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatFormFieldModule,
MatInputModule,
MatSnackBarModule,
MatProgressSpinnerModule,
ReactiveFormsModule
],
templateUrl: './risk-management.component.html',
styleUrl: './risk-management.component.css'
})
export class RiskManagementComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private fb = inject(FormBuilder);
private subscriptions: Subscription[] = [];
protected riskThresholds = signal<RiskThresholds | null>(null);
protected riskHistory = signal<RiskEvaluation[]>([]);
protected isLoading = signal<boolean>(true);
protected isSaving = signal<boolean>(false);
protected error = signal<string | null>(null);
protected thresholdsForm: FormGroup;
protected displayedColumns = ['symbol', 'positionValue', 'riskLevel', 'violations', 'timestamp'];
constructor() {
this.thresholdsForm = this.fb.group({
maxPositionSize: [0, [Validators.required, Validators.min(0)]],
maxDailyLoss: [0, [Validators.required, Validators.min(0)]],
maxPortfolioRisk: [0, [Validators.required, Validators.min(0), Validators.max(1)]],
volatilityLimit: [0, [Validators.required, Validators.min(0), Validators.max(1)]]
});
}
ngOnInit() {
this.loadRiskThresholds();
this.loadRiskHistory();
// Refresh risk history every 30 seconds
const historySubscription = interval(30000).subscribe(() => {
this.loadRiskHistory();
});
this.subscriptions.push(historySubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadRiskThresholds() {
this.apiService.getRiskThresholds().subscribe({
next: (response) => {
this.riskThresholds.set(response.data);
this.thresholdsForm.patchValue(response.data);
this.isLoading.set(false);
this.error.set(null);
},
error: (err) => {
console.error('Failed to load risk thresholds:', err);
this.error.set('Failed to load risk thresholds');
this.isLoading.set(false);
this.snackBar.open('Failed to load risk thresholds', 'Dismiss', { duration: 5000 });
}
});
}
private loadRiskHistory() {
this.apiService.getRiskHistory().subscribe({
next: (response) => {
this.riskHistory.set(response.data);
},
error: (err) => {
console.error('Failed to load risk history:', err);
this.snackBar.open('Failed to load risk history', 'Dismiss', { duration: 3000 });
}
});
}
saveThresholds() {
if (this.thresholdsForm.valid) {
this.isSaving.set(true);
const thresholds = this.thresholdsForm.value as RiskThresholds;
this.apiService.updateRiskThresholds(thresholds).subscribe({
next: (response) => {
this.riskThresholds.set(response.data);
this.isSaving.set(false);
this.snackBar.open('Risk thresholds updated successfully', 'Dismiss', { duration: 3000 });
},
error: (err) => {
console.error('Failed to save risk thresholds:', err);
this.isSaving.set(false);
this.snackBar.open('Failed to save risk thresholds', 'Dismiss', { duration: 5000 });
}
});
}
}
refreshData() {
this.isLoading.set(true);
this.loadRiskThresholds();
this.loadRiskHistory();
}
getRiskLevelColor(level: string): string {
switch (level) {
case 'LOW': return 'text-green-600';
case 'MEDIUM': return 'text-yellow-600';
case 'HIGH': return 'text-red-600';
default: return 'text-gray-600';
}
}
}
import { Component, signal, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiService, RiskThresholds, RiskEvaluation } from '../../services/api.service';
import { interval, Subscription } from 'rxjs';
@Component({
selector: 'app-risk-management',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatFormFieldModule,
MatInputModule,
MatSnackBarModule,
MatProgressSpinnerModule,
ReactiveFormsModule
],
templateUrl: './risk-management.component.html',
styleUrl: './risk-management.component.css'
})
export class RiskManagementComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private fb = inject(FormBuilder);
private subscriptions: Subscription[] = [];
protected riskThresholds = signal<RiskThresholds | null>(null);
protected riskHistory = signal<RiskEvaluation[]>([]);
protected isLoading = signal<boolean>(true);
protected isSaving = signal<boolean>(false);
protected error = signal<string | null>(null);
protected thresholdsForm: FormGroup;
protected displayedColumns = ['symbol', 'positionValue', 'riskLevel', 'violations', 'timestamp'];
constructor() {
this.thresholdsForm = this.fb.group({
maxPositionSize: [0, [Validators.required, Validators.min(0)]],
maxDailyLoss: [0, [Validators.required, Validators.min(0)]],
maxPortfolioRisk: [0, [Validators.required, Validators.min(0), Validators.max(1)]],
volatilityLimit: [0, [Validators.required, Validators.min(0), Validators.max(1)]]
});
}
ngOnInit() {
this.loadRiskThresholds();
this.loadRiskHistory();
// Refresh risk history every 30 seconds
const historySubscription = interval(30000).subscribe(() => {
this.loadRiskHistory();
});
this.subscriptions.push(historySubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadRiskThresholds() {
this.apiService.getRiskThresholds().subscribe({
next: (response) => {
this.riskThresholds.set(response.data);
this.thresholdsForm.patchValue(response.data);
this.isLoading.set(false);
this.error.set(null);
},
error: (err) => {
console.error('Failed to load risk thresholds:', err);
this.error.set('Failed to load risk thresholds');
this.isLoading.set(false);
this.snackBar.open('Failed to load risk thresholds', 'Dismiss', { duration: 5000 });
}
});
}
private loadRiskHistory() {
this.apiService.getRiskHistory().subscribe({
next: (response) => {
this.riskHistory.set(response.data);
},
error: (err) => {
console.error('Failed to load risk history:', err);
this.snackBar.open('Failed to load risk history', 'Dismiss', { duration: 3000 });
}
});
}
saveThresholds() {
if (this.thresholdsForm.valid) {
this.isSaving.set(true);
const thresholds = this.thresholdsForm.value as RiskThresholds;
this.apiService.updateRiskThresholds(thresholds).subscribe({
next: (response) => {
this.riskThresholds.set(response.data);
this.isSaving.set(false);
this.snackBar.open('Risk thresholds updated successfully', 'Dismiss', { duration: 3000 });
},
error: (err) => {
console.error('Failed to save risk thresholds:', err);
this.isSaving.set(false);
this.snackBar.open('Failed to save risk thresholds', 'Dismiss', { duration: 5000 });
}
});
}
}
refreshData() {
this.isLoading.set(true);
this.loadRiskThresholds();
this.loadRiskHistory();
}
getRiskLevelColor(level: string): string {
switch (level) {
case 'LOW': return 'text-green-600';
case 'MEDIUM': return 'text-yellow-600';
case 'HIGH': return 'text-red-600';
default: return 'text-gray-600';
}
}
}

View file

@ -1 +1 @@
/* Settings specific styles */
/* Settings specific styles */

View file

@ -1,15 +1,15 @@
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Settings</h1>
<p class="text-gray-600 mt-1">Configure application preferences and system settings</p>
</div>
</div>
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">settings</mat-icon>
<p class="mb-4">Application settings and configuration will be implemented here</p>
</div>
</mat-card>
</div>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Settings</h1>
<p class="text-gray-600 mt-1">Configure application preferences and system settings</p>
</div>
</div>
<mat-card class="p-6 h-96 flex items-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">settings</mat-icon>
<p class="mb-4">Application settings and configuration will be implemented here</p>
</div>
</mat-card>
</div>

View file

@ -1,13 +1,13 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-settings',
standalone: true,
imports: [CommonModule, MatCardModule, MatIconModule],
templateUrl: './settings.component.html',
styleUrl: './settings.component.css'
})
export class SettingsComponent {}
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-settings',
standalone: true,
imports: [CommonModule, MatCardModule, MatIconModule],
templateUrl: './settings.component.html',
styleUrl: './settings.component.css'
})
export class SettingsComponent {}

View file

@ -1,165 +1,165 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BacktestResult } from '../../../services/strategy.service';
import { Chart, ChartOptions } from 'chart.js/auto';
@Component({
selector: 'app-drawdown-chart',
standalone: true,
imports: [CommonModule],
template: `
<div class="drawdown-chart-container">
<canvas #drawdownChart></canvas>
</div>
`,
styles: `
.drawdown-chart-container {
width: 100%;
height: 300px;
margin-bottom: 20px;
}
`
})
export class DrawdownChartComponent implements OnChanges {
@Input() backtestResult?: BacktestResult;
private chart?: Chart;
private chartElement?: HTMLCanvasElement;
ngOnChanges(changes: SimpleChanges): void {
if (changes['backtestResult'] && this.backtestResult) {
this.renderChart();
}
}
ngAfterViewInit(): void {
this.chartElement = document.querySelector('canvas') as HTMLCanvasElement;
if (this.backtestResult) {
this.renderChart();
}
}
private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return;
// Clean up previous chart if it exists
if (this.chart) {
this.chart.destroy();
}
// Calculate drawdown series from daily returns
const drawdownData = this.calculateDrawdownSeries(this.backtestResult);
// Create chart
this.chart = new Chart(this.chartElement, {
type: 'line',
data: {
labels: drawdownData.dates.map(date => this.formatDate(date)),
datasets: [
{
label: 'Drawdown',
data: drawdownData.drawdowns,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true,
tension: 0.3,
borderWidth: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
maxTicksLimit: 12,
maxRotation: 0,
minRotation: 0
},
grid: {
display: false
}
},
y: {
ticks: {
callback: function(value) {
return (value * 100).toFixed(1) + '%';
}
},
grid: {
color: 'rgba(200, 200, 200, 0.2)'
},
min: -0.05, // Show at least 5% drawdown for context
suggestedMax: 0.01
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += (context.parsed.y * 100).toFixed(2) + '%';
}
return label;
}
}
},
legend: {
position: 'top',
}
}
} as ChartOptions
});
}
private calculateDrawdownSeries(result: BacktestResult): {
dates: Date[];
drawdowns: number[];
} {
const dates: Date[] = [];
const drawdowns: number[] = [];
// Sort daily returns by date
const sortedReturns = [...result.dailyReturns].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
// Calculate equity curve
let equity = 1;
const equityCurve: number[] = [];
for (const daily of sortedReturns) {
equity *= (1 + daily.return);
equityCurve.push(equity);
dates.push(new Date(daily.date));
}
// Calculate running maximum (high water mark)
let hwm = equityCurve[0];
for (let i = 0; i < equityCurve.length; i++) {
// Update high water mark
hwm = Math.max(hwm, equityCurve[i]);
// Calculate drawdown as percentage from high water mark
const drawdown = (equityCurve[i] / hwm) - 1;
drawdowns.push(drawdown);
}
return { dates, drawdowns };
}
private formatDate(date: Date): string {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
}
}
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BacktestResult } from '../../../services/strategy.service';
import { Chart, ChartOptions } from 'chart.js/auto';
@Component({
selector: 'app-drawdown-chart',
standalone: true,
imports: [CommonModule],
template: `
<div class="drawdown-chart-container">
<canvas #drawdownChart></canvas>
</div>
`,
styles: `
.drawdown-chart-container {
width: 100%;
height: 300px;
margin-bottom: 20px;
}
`
})
export class DrawdownChartComponent implements OnChanges {
@Input() backtestResult?: BacktestResult;
private chart?: Chart;
private chartElement?: HTMLCanvasElement;
ngOnChanges(changes: SimpleChanges): void {
if (changes['backtestResult'] && this.backtestResult) {
this.renderChart();
}
}
ngAfterViewInit(): void {
this.chartElement = document.querySelector('canvas') as HTMLCanvasElement;
if (this.backtestResult) {
this.renderChart();
}
}
private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return;
// Clean up previous chart if it exists
if (this.chart) {
this.chart.destroy();
}
// Calculate drawdown series from daily returns
const drawdownData = this.calculateDrawdownSeries(this.backtestResult);
// Create chart
this.chart = new Chart(this.chartElement, {
type: 'line',
data: {
labels: drawdownData.dates.map(date => this.formatDate(date)),
datasets: [
{
label: 'Drawdown',
data: drawdownData.drawdowns,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true,
tension: 0.3,
borderWidth: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
maxTicksLimit: 12,
maxRotation: 0,
minRotation: 0
},
grid: {
display: false
}
},
y: {
ticks: {
callback: function(value) {
return (value * 100).toFixed(1) + '%';
}
},
grid: {
color: 'rgba(200, 200, 200, 0.2)'
},
min: -0.05, // Show at least 5% drawdown for context
suggestedMax: 0.01
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += (context.parsed.y * 100).toFixed(2) + '%';
}
return label;
}
}
},
legend: {
position: 'top',
}
}
} as ChartOptions
});
}
private calculateDrawdownSeries(result: BacktestResult): {
dates: Date[];
drawdowns: number[];
} {
const dates: Date[] = [];
const drawdowns: number[] = [];
// Sort daily returns by date
const sortedReturns = [...result.dailyReturns].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
// Calculate equity curve
let equity = 1;
const equityCurve: number[] = [];
for (const daily of sortedReturns) {
equity *= (1 + daily.return);
equityCurve.push(equity);
dates.push(new Date(daily.date));
}
// Calculate running maximum (high water mark)
let hwm = equityCurve[0];
for (let i = 0; i < equityCurve.length; i++) {
// Update high water mark
hwm = Math.max(hwm, equityCurve[i]);
// Calculate drawdown as percentage from high water mark
const drawdown = (equityCurve[i] / hwm) - 1;
drawdowns.push(drawdown);
}
return { dates, drawdowns };
}
private formatDate(date: Date): string {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
}
}

View file

@ -1,171 +1,171 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BacktestResult } from '../../../services/strategy.service';
import { Chart, ChartOptions } from 'chart.js/auto';
@Component({
selector: 'app-equity-chart',
standalone: true,
imports: [CommonModule],
template: `
<div class="equity-chart-container">
<canvas #equityChart></canvas>
</div>
`,
styles: `
.equity-chart-container {
width: 100%;
height: 400px;
margin-bottom: 20px;
}
`
})
export class EquityChartComponent implements OnChanges {
@Input() backtestResult?: BacktestResult;
private chart?: Chart;
private chartElement?: HTMLCanvasElement;
ngOnChanges(changes: SimpleChanges): void {
if (changes['backtestResult'] && this.backtestResult) {
this.renderChart();
}
}
ngAfterViewInit(): void {
this.chartElement = document.querySelector('canvas') as HTMLCanvasElement;
if (this.backtestResult) {
this.renderChart();
}
}
private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return;
// Clean up previous chart if it exists
if (this.chart) {
this.chart.destroy();
}
// Prepare data
const equityCurve = this.calculateEquityCurve(this.backtestResult);
// Create chart
this.chart = new Chart(this.chartElement, {
type: 'line',
data: {
labels: equityCurve.dates.map(date => this.formatDate(date)),
datasets: [
{
label: 'Portfolio Value',
data: equityCurve.values,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.3,
borderWidth: 2,
fill: true
},
{
label: 'Benchmark',
data: equityCurve.benchmark,
borderColor: 'rgba(153, 102, 255, 0.5)',
backgroundColor: 'rgba(153, 102, 255, 0.1)',
borderDash: [5, 5],
tension: 0.3,
borderWidth: 1,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
maxTicksLimit: 12,
maxRotation: 0,
minRotation: 0
},
grid: {
display: false
}
},
y: {
ticks: {
callback: function(value) {
return '$' + value.toLocaleString();
}
},
grid: {
color: 'rgba(200, 200, 200, 0.2)'
}
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
.format(context.parsed.y);
}
return label;
}
}
},
legend: {
position: 'top',
}
}
} as ChartOptions
});
}
private calculateEquityCurve(result: BacktestResult): {
dates: Date[];
values: number[];
benchmark: number[];
} {
const initialValue = result.initialCapital;
const dates: Date[] = [];
const values: number[] = [];
const benchmark: number[] = [];
// Sort daily returns by date
const sortedReturns = [...result.dailyReturns].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
// Calculate cumulative portfolio values
let portfolioValue = initialValue;
let benchmarkValue = initialValue;
for (const daily of sortedReturns) {
const date = new Date(daily.date);
portfolioValue = portfolioValue * (1 + daily.return);
// Simple benchmark (e.g., assuming 8% annualized return for a market index)
benchmarkValue = benchmarkValue * (1 + 0.08 / 365);
dates.push(date);
values.push(portfolioValue);
benchmark.push(benchmarkValue);
}
return { dates, values, benchmark };
}
private formatDate(date: Date): string {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
}
}
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BacktestResult } from '../../../services/strategy.service';
import { Chart, ChartOptions } from 'chart.js/auto';
@Component({
selector: 'app-equity-chart',
standalone: true,
imports: [CommonModule],
template: `
<div class="equity-chart-container">
<canvas #equityChart></canvas>
</div>
`,
styles: `
.equity-chart-container {
width: 100%;
height: 400px;
margin-bottom: 20px;
}
`
})
export class EquityChartComponent implements OnChanges {
@Input() backtestResult?: BacktestResult;
private chart?: Chart;
private chartElement?: HTMLCanvasElement;
ngOnChanges(changes: SimpleChanges): void {
if (changes['backtestResult'] && this.backtestResult) {
this.renderChart();
}
}
ngAfterViewInit(): void {
this.chartElement = document.querySelector('canvas') as HTMLCanvasElement;
if (this.backtestResult) {
this.renderChart();
}
}
private renderChart(): void {
if (!this.chartElement || !this.backtestResult) return;
// Clean up previous chart if it exists
if (this.chart) {
this.chart.destroy();
}
// Prepare data
const equityCurve = this.calculateEquityCurve(this.backtestResult);
// Create chart
this.chart = new Chart(this.chartElement, {
type: 'line',
data: {
labels: equityCurve.dates.map(date => this.formatDate(date)),
datasets: [
{
label: 'Portfolio Value',
data: equityCurve.values,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.3,
borderWidth: 2,
fill: true
},
{
label: 'Benchmark',
data: equityCurve.benchmark,
borderColor: 'rgba(153, 102, 255, 0.5)',
backgroundColor: 'rgba(153, 102, 255, 0.1)',
borderDash: [5, 5],
tension: 0.3,
borderWidth: 1,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
maxTicksLimit: 12,
maxRotation: 0,
minRotation: 0
},
grid: {
display: false
}
},
y: {
ticks: {
callback: function(value) {
return '$' + value.toLocaleString();
}
},
grid: {
color: 'rgba(200, 200, 200, 0.2)'
}
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
.format(context.parsed.y);
}
return label;
}
}
},
legend: {
position: 'top',
}
}
} as ChartOptions
});
}
private calculateEquityCurve(result: BacktestResult): {
dates: Date[];
values: number[];
benchmark: number[];
} {
const initialValue = result.initialCapital;
const dates: Date[] = [];
const values: number[] = [];
const benchmark: number[] = [];
// Sort daily returns by date
const sortedReturns = [...result.dailyReturns].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
// Calculate cumulative portfolio values
let portfolioValue = initialValue;
let benchmarkValue = initialValue;
for (const daily of sortedReturns) {
const date = new Date(daily.date);
portfolioValue = portfolioValue * (1 + daily.return);
// Simple benchmark (e.g., assuming 8% annualized return for a market index)
benchmarkValue = benchmarkValue * (1 + 0.08 / 365);
dates.push(date);
values.push(portfolioValue);
benchmark.push(benchmarkValue);
}
return { dates, values, benchmark };
}
private formatDate(date: Date): string {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
}
}

View file

@ -1,258 +1,258 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatDividerModule } from '@angular/material/divider';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BacktestResult } from '../../../services/strategy.service';
@Component({
selector: 'app-performance-metrics',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatGridListModule,
MatDividerModule,
MatTooltipModule
],
template: `
<mat-card class="metrics-card">
<mat-card-header>
<mat-card-title>Performance Metrics</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="metrics-grid">
<div class="metric-group">
<h3>Returns</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Total return over the backtest period">Total Return</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.totalReturn || 0)">
{{formatPercent(backtestResult?.totalReturn || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Annualized return (adjusted for the backtest duration)">Annualized Return</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.annualizedReturn || 0)">
{{formatPercent(backtestResult?.annualizedReturn || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Compound Annual Growth Rate">CAGR</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.cagr || 0)">
{{formatPercent(backtestResult?.cagr || 0)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Risk Metrics</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Maximum peak-to-valley drawdown">Max Drawdown</div>
<div class="metric-value negative">
{{formatPercent(backtestResult?.maxDrawdown || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Number of days in the worst drawdown">Max DD Duration</div>
<div class="metric-value">
{{formatDays(backtestResult?.maxDrawdownDuration || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Annualized standard deviation of returns">Volatility</div>
<div class="metric-value">
{{formatPercent(backtestResult?.volatility || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Square root of the sum of the squares of drawdowns">Ulcer Index</div>
<div class="metric-value">
{{(backtestResult?.ulcerIndex || 0).toFixed(4)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Risk-Adjusted Returns</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Excess return per unit of risk">Sharpe Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.sharpeRatio || 0)">
{{(backtestResult?.sharpeRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Return per unit of downside risk">Sortino Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.sortinoRatio || 0)">
{{(backtestResult?.sortinoRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Return per unit of max drawdown">Calmar Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.calmarRatio || 0)">
{{(backtestResult?.calmarRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Probability-weighted ratio of gains vs. losses">Omega Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.omegaRatio || 0)">
{{(backtestResult?.omegaRatio || 0).toFixed(2)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Trade Statistics</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Total number of trades">Total Trades</div>
<div class="metric-value">
{{backtestResult?.totalTrades || 0}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Percentage of winning trades">Win Rate</div>
<div class="metric-value" [ngClass]="getWinRateClass(backtestResult?.winRate || 0)">
{{formatPercent(backtestResult?.winRate || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Average profit of winning trades">Avg Win</div>
<div class="metric-value positive">
{{formatPercent(backtestResult?.averageWinningTrade || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Average loss of losing trades">Avg Loss</div>
<div class="metric-value negative">
{{formatPercent(backtestResult?.averageLosingTrade || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Ratio of total gains to total losses">Profit Factor</div>
<div class="metric-value" [ngClass]="getProfitFactorClass(backtestResult?.profitFactor || 0)">
{{(backtestResult?.profitFactor || 0).toFixed(2)}}
</div>
</div>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
`,
styles: `
.metrics-card {
margin-bottom: 20px;
}
.metrics-grid {
display: flex;
flex-direction: column;
gap: 16px;
}
.metric-group {
padding: 10px 0;
}
.metric-group h3 {
margin-top: 0;
margin-bottom: 16px;
font-size: 16px;
font-weight: 500;
color: #555;
}
.metrics-row {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.metric {
min-width: 120px;
margin-bottom: 16px;
}
.metric-name {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.metric-value {
font-size: 16px;
font-weight: 500;
}
.positive {
color: #4CAF50;
}
.negative {
color: #F44336;
}
.neutral {
color: #FFA000;
}
mat-divider {
margin: 8px 0;
}
`
})
export class PerformanceMetricsComponent {
@Input() backtestResult?: BacktestResult;
// Formatting helpers
formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
formatDays(days: number): string {
return `${days} days`;
}
// Conditional classes
getReturnClass(value: number): string {
if (value > 0) return 'positive';
if (value < 0) return 'negative';
return '';
}
getRatioClass(value: number): string {
if (value >= 1.5) return 'positive';
if (value >= 1) return 'neutral';
if (value < 0) return 'negative';
return '';
}
getWinRateClass(value: number): string {
if (value >= 0.55) return 'positive';
if (value >= 0.45) return 'neutral';
return 'negative';
}
getProfitFactorClass(value: number): string {
if (value >= 1.5) return 'positive';
if (value >= 1) return 'neutral';
return 'negative';
}
}
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatDividerModule } from '@angular/material/divider';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BacktestResult } from '../../../services/strategy.service';
@Component({
selector: 'app-performance-metrics',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatGridListModule,
MatDividerModule,
MatTooltipModule
],
template: `
<mat-card class="metrics-card">
<mat-card-header>
<mat-card-title>Performance Metrics</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="metrics-grid">
<div class="metric-group">
<h3>Returns</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Total return over the backtest period">Total Return</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.totalReturn || 0)">
{{formatPercent(backtestResult?.totalReturn || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Annualized return (adjusted for the backtest duration)">Annualized Return</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.annualizedReturn || 0)">
{{formatPercent(backtestResult?.annualizedReturn || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Compound Annual Growth Rate">CAGR</div>
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.cagr || 0)">
{{formatPercent(backtestResult?.cagr || 0)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Risk Metrics</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Maximum peak-to-valley drawdown">Max Drawdown</div>
<div class="metric-value negative">
{{formatPercent(backtestResult?.maxDrawdown || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Number of days in the worst drawdown">Max DD Duration</div>
<div class="metric-value">
{{formatDays(backtestResult?.maxDrawdownDuration || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Annualized standard deviation of returns">Volatility</div>
<div class="metric-value">
{{formatPercent(backtestResult?.volatility || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Square root of the sum of the squares of drawdowns">Ulcer Index</div>
<div class="metric-value">
{{(backtestResult?.ulcerIndex || 0).toFixed(4)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Risk-Adjusted Returns</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Excess return per unit of risk">Sharpe Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.sharpeRatio || 0)">
{{(backtestResult?.sharpeRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Return per unit of downside risk">Sortino Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.sortinoRatio || 0)">
{{(backtestResult?.sortinoRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Return per unit of max drawdown">Calmar Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.calmarRatio || 0)">
{{(backtestResult?.calmarRatio || 0).toFixed(2)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Probability-weighted ratio of gains vs. losses">Omega Ratio</div>
<div class="metric-value" [ngClass]="getRatioClass(backtestResult?.omegaRatio || 0)">
{{(backtestResult?.omegaRatio || 0).toFixed(2)}}
</div>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div class="metric-group">
<h3>Trade Statistics</h3>
<div class="metrics-row">
<div class="metric">
<div class="metric-name" matTooltip="Total number of trades">Total Trades</div>
<div class="metric-value">
{{backtestResult?.totalTrades || 0}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Percentage of winning trades">Win Rate</div>
<div class="metric-value" [ngClass]="getWinRateClass(backtestResult?.winRate || 0)">
{{formatPercent(backtestResult?.winRate || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Average profit of winning trades">Avg Win</div>
<div class="metric-value positive">
{{formatPercent(backtestResult?.averageWinningTrade || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Average loss of losing trades">Avg Loss</div>
<div class="metric-value negative">
{{formatPercent(backtestResult?.averageLosingTrade || 0)}}
</div>
</div>
<div class="metric">
<div class="metric-name" matTooltip="Ratio of total gains to total losses">Profit Factor</div>
<div class="metric-value" [ngClass]="getProfitFactorClass(backtestResult?.profitFactor || 0)">
{{(backtestResult?.profitFactor || 0).toFixed(2)}}
</div>
</div>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
`,
styles: `
.metrics-card {
margin-bottom: 20px;
}
.metrics-grid {
display: flex;
flex-direction: column;
gap: 16px;
}
.metric-group {
padding: 10px 0;
}
.metric-group h3 {
margin-top: 0;
margin-bottom: 16px;
font-size: 16px;
font-weight: 500;
color: #555;
}
.metrics-row {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.metric {
min-width: 120px;
margin-bottom: 16px;
}
.metric-name {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.metric-value {
font-size: 16px;
font-weight: 500;
}
.positive {
color: #4CAF50;
}
.negative {
color: #F44336;
}
.neutral {
color: #FFA000;
}
mat-divider {
margin: 8px 0;
}
`
})
export class PerformanceMetricsComponent {
@Input() backtestResult?: BacktestResult;
// Formatting helpers
formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
formatDays(days: number): string {
return `${days} days`;
}
// Conditional classes
getReturnClass(value: number): string {
if (value > 0) return 'positive';
if (value < 0) return 'negative';
return '';
}
getRatioClass(value: number): string {
if (value >= 1.5) return 'positive';
if (value >= 1) return 'neutral';
if (value < 0) return 'negative';
return '';
}
getWinRateClass(value: number): string {
if (value >= 0.55) return 'positive';
if (value >= 0.45) return 'neutral';
return 'negative';
}
getProfitFactorClass(value: number): string {
if (value >= 1.5) return 'positive';
if (value >= 1) return 'neutral';
return 'negative';
}
}

View file

@ -1,221 +1,221 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule, Sort } from '@angular/material/sort';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { BacktestResult } from '../../../services/strategy.service';
@Component({
selector: 'app-trades-table',
standalone: true,
imports: [
CommonModule,
MatTableModule,
MatSortModule,
MatPaginatorModule,
MatCardModule,
MatIconModule
],
template: `
<mat-card class="trades-card">
<mat-card-header>
<mat-card-title>Trades</mat-card-title>
</mat-card-header>
<mat-card-content>
<table mat-table [dataSource]="displayedTrades" matSort (matSortChange)="sortData($event)" class="trades-table">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </th>
<td mat-cell *matCellDef="let trade"> {{trade.symbol}} </td>
</ng-container>
<!-- Entry Date Column -->
<ng-container matColumnDef="entryTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entry Time </th>
<td mat-cell *matCellDef="let trade"> {{formatDate(trade.entryTime)}} </td>
</ng-container>
<!-- Entry Price Column -->
<ng-container matColumnDef="entryPrice">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entry Price </th>
<td mat-cell *matCellDef="let trade"> {{formatCurrency(trade.entryPrice)}} </td>
</ng-container>
<!-- Exit Date Column -->
<ng-container matColumnDef="exitTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Exit Time </th>
<td mat-cell *matCellDef="let trade"> {{formatDate(trade.exitTime)}} </td>
</ng-container>
<!-- Exit Price Column -->
<ng-container matColumnDef="exitPrice">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Exit Price </th>
<td mat-cell *matCellDef="let trade"> {{formatCurrency(trade.exitPrice)}} </td>
</ng-container>
<!-- Quantity Column -->
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Quantity </th>
<td mat-cell *matCellDef="let trade"> {{trade.quantity}} </td>
</ng-container>
<!-- P&L Column -->
<ng-container matColumnDef="pnl">
<th mat-header-cell *matHeaderCellDef mat-sort-header> P&L </th>
<td mat-cell *matCellDef="let trade"
[ngClass]="{'positive': trade.pnl > 0, 'negative': trade.pnl < 0}">
{{formatCurrency(trade.pnl)}}
</td>
</ng-container>
<!-- P&L Percent Column -->
<ng-container matColumnDef="pnlPercent">
<th mat-header-cell *matHeaderCellDef mat-sort-header> P&L % </th>
<td mat-cell *matCellDef="let trade"
[ngClass]="{'positive': trade.pnlPercent > 0, 'negative': trade.pnlPercent < 0}">
{{formatPercent(trade.pnlPercent)}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator
[length]="totalTrades"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 50]"
(page)="pageChange($event)"
aria-label="Select page">
</mat-paginator>
</mat-card-content>
</mat-card>
`,
styles: `
.trades-card {
margin-bottom: 20px;
}
.trades-table {
width: 100%;
border-collapse: collapse;
}
.mat-column-pnl, .mat-column-pnlPercent {
text-align: right;
font-weight: 500;
}
.positive {
color: #4CAF50;
}
.negative {
color: #F44336;
}
.mat-mdc-row:hover {
background-color: rgba(0, 0, 0, 0.04);
}
`
})
export class TradesTableComponent {
@Input() set backtestResult(value: BacktestResult | undefined) {
if (value) {
this._backtestResult = value;
this.updateDisplayedTrades();
}
}
get backtestResult(): BacktestResult | undefined {
return this._backtestResult;
}
private _backtestResult?: BacktestResult;
// Table configuration
displayedColumns: string[] = [
'symbol', 'entryTime', 'entryPrice', 'exitTime',
'exitPrice', 'quantity', 'pnl', 'pnlPercent'
];
// Pagination
pageSize = 10;
currentPage = 0;
displayedTrades: any[] = [];
get totalTrades(): number {
return this._backtestResult?.trades.length || 0;
}
// Sort the trades
sortData(sort: Sort): void {
if (!sort.active || sort.direction === '') {
this.updateDisplayedTrades();
return;
}
const data = this._backtestResult?.trades.slice() || [];
this.displayedTrades = data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch (sort.active) {
case 'symbol': return this.compare(a.symbol, b.symbol, isAsc);
case 'entryTime': return this.compare(new Date(a.entryTime).getTime(), new Date(b.entryTime).getTime(), isAsc);
case 'entryPrice': return this.compare(a.entryPrice, b.entryPrice, isAsc);
case 'exitTime': return this.compare(new Date(a.exitTime).getTime(), new Date(b.exitTime).getTime(), isAsc);
case 'exitPrice': return this.compare(a.exitPrice, b.exitPrice, isAsc);
case 'quantity': return this.compare(a.quantity, b.quantity, isAsc);
case 'pnl': return this.compare(a.pnl, b.pnl, isAsc);
case 'pnlPercent': return this.compare(a.pnlPercent, b.pnlPercent, isAsc);
default: return 0;
}
}).slice(this.currentPage * this.pageSize, (this.currentPage + 1) * this.pageSize);
}
// Handle page changes
pageChange(event: PageEvent): void {
this.pageSize = event.pageSize;
this.currentPage = event.pageIndex;
this.updateDisplayedTrades();
}
// Update displayed trades based on current page and page size
updateDisplayedTrades(): void {
if (this._backtestResult) {
this.displayedTrades = this._backtestResult.trades.slice(
this.currentPage * this.pageSize,
(this.currentPage + 1) * this.pageSize
);
} else {
this.displayedTrades = [];
}
}
// Helper methods for formatting
formatDate(date: Date | string): string {
return new Date(date).toLocaleString();
}
formatCurrency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value);
}
formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
private compare(a: number | string, b: number | string, isAsc: boolean): number {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
}
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule, Sort } from '@angular/material/sort';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { BacktestResult } from '../../../services/strategy.service';
@Component({
selector: 'app-trades-table',
standalone: true,
imports: [
CommonModule,
MatTableModule,
MatSortModule,
MatPaginatorModule,
MatCardModule,
MatIconModule
],
template: `
<mat-card class="trades-card">
<mat-card-header>
<mat-card-title>Trades</mat-card-title>
</mat-card-header>
<mat-card-content>
<table mat-table [dataSource]="displayedTrades" matSort (matSortChange)="sortData($event)" class="trades-table">
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </th>
<td mat-cell *matCellDef="let trade"> {{trade.symbol}} </td>
</ng-container>
<!-- Entry Date Column -->
<ng-container matColumnDef="entryTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entry Time </th>
<td mat-cell *matCellDef="let trade"> {{formatDate(trade.entryTime)}} </td>
</ng-container>
<!-- Entry Price Column -->
<ng-container matColumnDef="entryPrice">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entry Price </th>
<td mat-cell *matCellDef="let trade"> {{formatCurrency(trade.entryPrice)}} </td>
</ng-container>
<!-- Exit Date Column -->
<ng-container matColumnDef="exitTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Exit Time </th>
<td mat-cell *matCellDef="let trade"> {{formatDate(trade.exitTime)}} </td>
</ng-container>
<!-- Exit Price Column -->
<ng-container matColumnDef="exitPrice">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Exit Price </th>
<td mat-cell *matCellDef="let trade"> {{formatCurrency(trade.exitPrice)}} </td>
</ng-container>
<!-- Quantity Column -->
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Quantity </th>
<td mat-cell *matCellDef="let trade"> {{trade.quantity}} </td>
</ng-container>
<!-- P&L Column -->
<ng-container matColumnDef="pnl">
<th mat-header-cell *matHeaderCellDef mat-sort-header> P&L </th>
<td mat-cell *matCellDef="let trade"
[ngClass]="{'positive': trade.pnl > 0, 'negative': trade.pnl < 0}">
{{formatCurrency(trade.pnl)}}
</td>
</ng-container>
<!-- P&L Percent Column -->
<ng-container matColumnDef="pnlPercent">
<th mat-header-cell *matHeaderCellDef mat-sort-header> P&L % </th>
<td mat-cell *matCellDef="let trade"
[ngClass]="{'positive': trade.pnlPercent > 0, 'negative': trade.pnlPercent < 0}">
{{formatPercent(trade.pnlPercent)}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator
[length]="totalTrades"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 50]"
(page)="pageChange($event)"
aria-label="Select page">
</mat-paginator>
</mat-card-content>
</mat-card>
`,
styles: `
.trades-card {
margin-bottom: 20px;
}
.trades-table {
width: 100%;
border-collapse: collapse;
}
.mat-column-pnl, .mat-column-pnlPercent {
text-align: right;
font-weight: 500;
}
.positive {
color: #4CAF50;
}
.negative {
color: #F44336;
}
.mat-mdc-row:hover {
background-color: rgba(0, 0, 0, 0.04);
}
`
})
export class TradesTableComponent {
@Input() set backtestResult(value: BacktestResult | undefined) {
if (value) {
this._backtestResult = value;
this.updateDisplayedTrades();
}
}
get backtestResult(): BacktestResult | undefined {
return this._backtestResult;
}
private _backtestResult?: BacktestResult;
// Table configuration
displayedColumns: string[] = [
'symbol', 'entryTime', 'entryPrice', 'exitTime',
'exitPrice', 'quantity', 'pnl', 'pnlPercent'
];
// Pagination
pageSize = 10;
currentPage = 0;
displayedTrades: any[] = [];
get totalTrades(): number {
return this._backtestResult?.trades.length || 0;
}
// Sort the trades
sortData(sort: Sort): void {
if (!sort.active || sort.direction === '') {
this.updateDisplayedTrades();
return;
}
const data = this._backtestResult?.trades.slice() || [];
this.displayedTrades = data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch (sort.active) {
case 'symbol': return this.compare(a.symbol, b.symbol, isAsc);
case 'entryTime': return this.compare(new Date(a.entryTime).getTime(), new Date(b.entryTime).getTime(), isAsc);
case 'entryPrice': return this.compare(a.entryPrice, b.entryPrice, isAsc);
case 'exitTime': return this.compare(new Date(a.exitTime).getTime(), new Date(b.exitTime).getTime(), isAsc);
case 'exitPrice': return this.compare(a.exitPrice, b.exitPrice, isAsc);
case 'quantity': return this.compare(a.quantity, b.quantity, isAsc);
case 'pnl': return this.compare(a.pnl, b.pnl, isAsc);
case 'pnlPercent': return this.compare(a.pnlPercent, b.pnlPercent, isAsc);
default: return 0;
}
}).slice(this.currentPage * this.pageSize, (this.currentPage + 1) * this.pageSize);
}
// Handle page changes
pageChange(event: PageEvent): void {
this.pageSize = event.pageSize;
this.currentPage = event.pageIndex;
this.updateDisplayedTrades();
}
// Update displayed trades based on current page and page size
updateDisplayedTrades(): void {
if (this._backtestResult) {
this.displayedTrades = this._backtestResult.trades.slice(
this.currentPage * this.pageSize,
(this.currentPage + 1) * this.pageSize
);
} else {
this.displayedTrades = [];
}
}
// Helper methods for formatting
formatDate(date: Date | string): string {
return new Date(date).toLocaleString();
}
formatCurrency(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value);
}
formatPercent(value: number): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
private compare(a: number | string, b: number | string, isAsc: boolean): number {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
}

View file

@ -1,185 +1,185 @@
import { Component, Inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import {
BacktestRequest,
BacktestResult,
StrategyService,
TradingStrategy
} from '../../../services/strategy.service';
@Component({
selector: 'app-backtest-dialog',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatProgressBarModule,
MatTabsModule,
MatChipsModule,
MatIconModule,
MatSlideToggleModule
],
templateUrl: './backtest-dialog.component.html',
styleUrl: './backtest-dialog.component.css'
})
export class BacktestDialogComponent implements OnInit {
backtestForm: FormGroup;
strategyTypes: string[] = [];
availableSymbols: string[] = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'SPY', 'QQQ'];
selectedSymbols: string[] = [];
parameters: Record<string, any> = {};
isRunning: boolean = false;
backtestResult: BacktestResult | null = null;
constructor(
private fb: FormBuilder,
private strategyService: StrategyService,
@Inject(MAT_DIALOG_DATA) public data: TradingStrategy | null,
private dialogRef: MatDialogRef<BacktestDialogComponent>
) {
// Initialize form with defaults
this.backtestForm = this.fb.group({
strategyType: ['', [Validators.required]],
startDate: [new Date(new Date().setFullYear(new Date().getFullYear() - 1)), [Validators.required]],
endDate: [new Date(), [Validators.required]],
initialCapital: [100000, [Validators.required, Validators.min(1000)]],
dataResolution: ['1d', [Validators.required]],
commission: [0.001, [Validators.required, Validators.min(0), Validators.max(0.1)]],
slippage: [0.0005, [Validators.required, Validators.min(0), Validators.max(0.1)]],
mode: ['event', [Validators.required]]
});
// If strategy is provided, pre-populate the form
if (data) {
this.selectedSymbols = [...data.symbols];
this.backtestForm.patchValue({
strategyType: data.type
});
this.parameters = {...data.parameters};
}
}
ngOnInit(): void {
this.loadStrategyTypes();
}
loadStrategyTypes(): void {
this.strategyService.getStrategyTypes().subscribe({
next: (response) => {
if (response.success) {
this.strategyTypes = response.data;
// If strategy is provided, load its parameters
if (this.data) {
this.onStrategyTypeChange(this.data.type);
}
}
},
error: (error) => {
console.error('Error loading strategy types:', error);
this.strategyTypes = ['MOVING_AVERAGE_CROSSOVER', 'MEAN_REVERSION', 'CUSTOM'];
}
});
}
onStrategyTypeChange(type: string): void {
// Get default parameters for this strategy type
this.strategyService.getStrategyParameters(type).subscribe({
next: (response) => {
if (response.success) {
// If strategy is provided, merge default with existing
if (this.data) {
this.parameters = {
...response.data,
...this.data.parameters
};
} else {
this.parameters = response.data;
}
}
},
error: (error) => {
console.error('Error loading parameters:', error);
this.parameters = {};
}
});
}
addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return;
this.selectedSymbols.push(symbol);
}
removeSymbol(symbol: string): void {
this.selectedSymbols = this.selectedSymbols.filter(s => s !== symbol);
}
updateParameter(key: string, value: any): void {
this.parameters[key] = value;
}
onSubmit(): void {
if (this.backtestForm.invalid || this.selectedSymbols.length === 0) {
return;
}
const formValue = this.backtestForm.value;
const backtestRequest: BacktestRequest = {
strategyType: formValue.strategyType,
strategyParams: this.parameters,
symbols: this.selectedSymbols,
startDate: formValue.startDate,
endDate: formValue.endDate,
initialCapital: formValue.initialCapital,
dataResolution: formValue.dataResolution,
commission: formValue.commission,
slippage: formValue.slippage,
mode: formValue.mode
};
this.isRunning = true;
this.strategyService.runBacktest(backtestRequest).subscribe({
next: (response) => {
this.isRunning = false;
if (response.success) {
this.backtestResult = response.data;
}
},
error: (error) => {
this.isRunning = false;
console.error('Backtest error:', error);
}
});
}
close(): void {
this.dialogRef.close(this.backtestResult);
}
}
import { Component, Inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import {
BacktestRequest,
BacktestResult,
StrategyService,
TradingStrategy
} from '../../../services/strategy.service';
@Component({
selector: 'app-backtest-dialog',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatProgressBarModule,
MatTabsModule,
MatChipsModule,
MatIconModule,
MatSlideToggleModule
],
templateUrl: './backtest-dialog.component.html',
styleUrl: './backtest-dialog.component.css'
})
export class BacktestDialogComponent implements OnInit {
backtestForm: FormGroup;
strategyTypes: string[] = [];
availableSymbols: string[] = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'SPY', 'QQQ'];
selectedSymbols: string[] = [];
parameters: Record<string, any> = {};
isRunning: boolean = false;
backtestResult: BacktestResult | null = null;
constructor(
private fb: FormBuilder,
private strategyService: StrategyService,
@Inject(MAT_DIALOG_DATA) public data: TradingStrategy | null,
private dialogRef: MatDialogRef<BacktestDialogComponent>
) {
// Initialize form with defaults
this.backtestForm = this.fb.group({
strategyType: ['', [Validators.required]],
startDate: [new Date(new Date().setFullYear(new Date().getFullYear() - 1)), [Validators.required]],
endDate: [new Date(), [Validators.required]],
initialCapital: [100000, [Validators.required, Validators.min(1000)]],
dataResolution: ['1d', [Validators.required]],
commission: [0.001, [Validators.required, Validators.min(0), Validators.max(0.1)]],
slippage: [0.0005, [Validators.required, Validators.min(0), Validators.max(0.1)]],
mode: ['event', [Validators.required]]
});
// If strategy is provided, pre-populate the form
if (data) {
this.selectedSymbols = [...data.symbols];
this.backtestForm.patchValue({
strategyType: data.type
});
this.parameters = {...data.parameters};
}
}
ngOnInit(): void {
this.loadStrategyTypes();
}
loadStrategyTypes(): void {
this.strategyService.getStrategyTypes().subscribe({
next: (response) => {
if (response.success) {
this.strategyTypes = response.data;
// If strategy is provided, load its parameters
if (this.data) {
this.onStrategyTypeChange(this.data.type);
}
}
},
error: (error) => {
console.error('Error loading strategy types:', error);
this.strategyTypes = ['MOVING_AVERAGE_CROSSOVER', 'MEAN_REVERSION', 'CUSTOM'];
}
});
}
onStrategyTypeChange(type: string): void {
// Get default parameters for this strategy type
this.strategyService.getStrategyParameters(type).subscribe({
next: (response) => {
if (response.success) {
// If strategy is provided, merge default with existing
if (this.data) {
this.parameters = {
...response.data,
...this.data.parameters
};
} else {
this.parameters = response.data;
}
}
},
error: (error) => {
console.error('Error loading parameters:', error);
this.parameters = {};
}
});
}
addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return;
this.selectedSymbols.push(symbol);
}
removeSymbol(symbol: string): void {
this.selectedSymbols = this.selectedSymbols.filter(s => s !== symbol);
}
updateParameter(key: string, value: any): void {
this.parameters[key] = value;
}
onSubmit(): void {
if (this.backtestForm.invalid || this.selectedSymbols.length === 0) {
return;
}
const formValue = this.backtestForm.value;
const backtestRequest: BacktestRequest = {
strategyType: formValue.strategyType,
strategyParams: this.parameters,
symbols: this.selectedSymbols,
startDate: formValue.startDate,
endDate: formValue.endDate,
initialCapital: formValue.initialCapital,
dataResolution: formValue.dataResolution,
commission: formValue.commission,
slippage: formValue.slippage,
mode: formValue.mode
};
this.isRunning = true;
this.strategyService.runBacktest(backtestRequest).subscribe({
next: (response) => {
this.isRunning = false;
if (response.success) {
this.backtestResult = response.data;
}
},
error: (error) => {
this.isRunning = false;
console.error('Backtest error:', error);
}
});
}
close(): void {
this.dialogRef.close(this.backtestResult);
}
}

View file

@ -1,84 +1,84 @@
<h2 mat-dialog-title>{{isEditMode ? 'Edit Strategy' : 'Create Strategy'}}</h2>
<form [formGroup]="strategyForm" (ngSubmit)="onSubmit()">
<mat-dialog-content class="mat-typography">
<div class="grid grid-cols-1 gap-4">
<!-- Basic Strategy Information -->
<mat-form-field appearance="outline" class="w-full">
<mat-label>Strategy Name</mat-label>
<input matInput formControlName="name" placeholder="e.g., My Moving Average Crossover">
<mat-error *ngIf="strategyForm.get('name')?.invalid">Name is required</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Description</mat-label>
<textarea matInput formControlName="description" rows="3"
placeholder="Describe what this strategy does..."></textarea>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Strategy Type</mat-label>
<mat-select formControlName="type" (selectionChange)="onStrategyTypeChange($event.value)">
<mat-option *ngFor="let type of strategyTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
<mat-error *ngIf="strategyForm.get('type')?.invalid">Strategy type is required</mat-error>
</mat-form-field>
<!-- Symbol Selection -->
<div class="w-full">
<label class="text-sm">Trading Symbols</label>
<div class="flex flex-wrap gap-2 mb-2">
<mat-chip *ngFor="let symbol of selectedSymbols" [removable]="true"
(removed)="removeSymbol(symbol)">
{{symbol}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</div>
<div class="flex gap-2">
<mat-form-field appearance="outline" class="flex-1">
<mat-label>Add Symbol</mat-label>
<input matInput #symbolInput placeholder="e.g., AAPL">
</mat-form-field>
<button type="button" mat-raised-button color="primary"
(click)="addSymbol(symbolInput.value); symbolInput.value = ''">
Add
</button>
</div>
<div class="mt-2">
<p class="text-sm text-gray-500 mb-1">Suggested symbols:</p>
<div class="flex flex-wrap gap-2">
<button type="button" *ngFor="let symbol of availableSymbols"
mat-stroked-button (click)="addSymbol(symbol)"
[disabled]="selectedSymbols.includes(symbol)">
{{symbol}}
</button>
</div>
</div>
</div>
<!-- Dynamic Strategy Parameters -->
<div *ngIf="strategyForm.get('type')?.value && Object.keys(parameters).length > 0">
<h3 class="text-lg font-semibold mb-2">Strategy Parameters</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field *ngFor="let param of parameters | keyvalue" appearance="outline">
<mat-label>{{param.key}}</mat-label>
<input matInput [value]="param.value"
(input)="updateParameter(param.key, $any($event.target).value)">
</mat-form-field>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-raised-button color="primary" type="submit"
[disabled]="strategyForm.invalid || selectedSymbols.length === 0">
{{isEditMode ? 'Update' : 'Create'}}
</button>
</mat-dialog-actions>
</form>
<h2 mat-dialog-title>{{isEditMode ? 'Edit Strategy' : 'Create Strategy'}}</h2>
<form [formGroup]="strategyForm" (ngSubmit)="onSubmit()">
<mat-dialog-content class="mat-typography">
<div class="grid grid-cols-1 gap-4">
<!-- Basic Strategy Information -->
<mat-form-field appearance="outline" class="w-full">
<mat-label>Strategy Name</mat-label>
<input matInput formControlName="name" placeholder="e.g., My Moving Average Crossover">
<mat-error *ngIf="strategyForm.get('name')?.invalid">Name is required</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Description</mat-label>
<textarea matInput formControlName="description" rows="3"
placeholder="Describe what this strategy does..."></textarea>
</mat-form-field>
<mat-form-field appearance="outline" class="w-full">
<mat-label>Strategy Type</mat-label>
<mat-select formControlName="type" (selectionChange)="onStrategyTypeChange($event.value)">
<mat-option *ngFor="let type of strategyTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
<mat-error *ngIf="strategyForm.get('type')?.invalid">Strategy type is required</mat-error>
</mat-form-field>
<!-- Symbol Selection -->
<div class="w-full">
<label class="text-sm">Trading Symbols</label>
<div class="flex flex-wrap gap-2 mb-2">
<mat-chip *ngFor="let symbol of selectedSymbols" [removable]="true"
(removed)="removeSymbol(symbol)">
{{symbol}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</div>
<div class="flex gap-2">
<mat-form-field appearance="outline" class="flex-1">
<mat-label>Add Symbol</mat-label>
<input matInput #symbolInput placeholder="e.g., AAPL">
</mat-form-field>
<button type="button" mat-raised-button color="primary"
(click)="addSymbol(symbolInput.value); symbolInput.value = ''">
Add
</button>
</div>
<div class="mt-2">
<p class="text-sm text-gray-500 mb-1">Suggested symbols:</p>
<div class="flex flex-wrap gap-2">
<button type="button" *ngFor="let symbol of availableSymbols"
mat-stroked-button (click)="addSymbol(symbol)"
[disabled]="selectedSymbols.includes(symbol)">
{{symbol}}
</button>
</div>
</div>
</div>
<!-- Dynamic Strategy Parameters -->
<div *ngIf="strategyForm.get('type')?.value && Object.keys(parameters).length > 0">
<h3 class="text-lg font-semibold mb-2">Strategy Parameters</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field *ngFor="let param of parameters | keyvalue" appearance="outline">
<mat-label>{{param.key}}</mat-label>
<input matInput [value]="param.value"
(input)="updateParameter(param.key, $any($event.target).value)">
</mat-form-field>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-raised-button color="primary" type="submit"
[disabled]="strategyForm.invalid || selectedSymbols.length === 0">
{{isEditMode ? 'Update' : 'Create'}}
</button>
</mat-dialog-actions>
</form>

View file

@ -1,178 +1,178 @@
import { Component, Inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import {
StrategyService,
TradingStrategy
} from '../../../services/strategy.service';
@Component({
selector: 'app-strategy-dialog',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatChipsModule,
MatIconModule,
MatAutocompleteModule
],
templateUrl: './strategy-dialog.component.html',
styleUrl: './strategy-dialog.component.css'
})
export class StrategyDialogComponent implements OnInit {
strategyForm: FormGroup;
isEditMode: boolean = false;
strategyTypes: string[] = [];
availableSymbols: string[] = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'SPY', 'QQQ'];
selectedSymbols: string[] = [];
separatorKeysCodes: number[] = [ENTER, COMMA];
parameters: Record<string, any> = {};
constructor(
private fb: FormBuilder,
private strategyService: StrategyService,
@Inject(MAT_DIALOG_DATA) public data: TradingStrategy | null,
private dialogRef: MatDialogRef<StrategyDialogComponent>
) {
this.isEditMode = !!data;
this.strategyForm = this.fb.group({
name: ['', [Validators.required]],
description: [''],
type: ['', [Validators.required]],
// Dynamic parameters will be added based on strategy type
});
if (this.isEditMode && data) {
this.selectedSymbols = [...data.symbols];
this.strategyForm.patchValue({
name: data.name,
description: data.description,
type: data.type
});
this.parameters = {...data.parameters};
}
}
ngOnInit(): void {
// In a real implementation, fetch available strategy types from the API
this.loadStrategyTypes();
}
loadStrategyTypes(): void {
// In a real implementation, this would call the API
this.strategyService.getStrategyTypes().subscribe({
next: (response) => {
if (response.success) {
this.strategyTypes = response.data;
// If editing, load parameters
if (this.isEditMode && this.data) {
this.onStrategyTypeChange(this.data.type);
}
}
},
error: (error) => {
console.error('Error loading strategy types:', error);
// Fallback to hardcoded types
this.strategyTypes = ['MOVING_AVERAGE_CROSSOVER', 'MEAN_REVERSION', 'CUSTOM'];
}
});
}
onStrategyTypeChange(type: string): void {
// Get default parameters for this strategy type
this.strategyService.getStrategyParameters(type).subscribe({
next: (response) => {
if (response.success) {
// If editing, merge default with existing
if (this.isEditMode && this.data) {
this.parameters = {
...response.data,
...this.data.parameters
};
} else {
this.parameters = response.data;
}
}
},
error: (error) => {
console.error('Error loading parameters:', error);
// Fallback to empty parameters
this.parameters = {};
}
});
}
addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return;
this.selectedSymbols.push(symbol);
}
removeSymbol(symbol: string): void {
this.selectedSymbols = this.selectedSymbols.filter(s => s !== symbol);
}
onSubmit(): void {
if (this.strategyForm.invalid || this.selectedSymbols.length === 0) {
return;
}
const formValue = this.strategyForm.value;
const strategy: Partial<TradingStrategy> = {
name: formValue.name,
description: formValue.description,
type: formValue.type,
symbols: this.selectedSymbols,
parameters: this.parameters,
};
if (this.isEditMode && this.data) {
this.strategyService.updateStrategy(this.data.id, strategy).subscribe({
next: (response) => {
if (response.success) {
this.dialogRef.close(true);
}
},
error: (error) => {
console.error('Error updating strategy:', error);
}
});
} else {
this.strategyService.createStrategy(strategy).subscribe({
next: (response) => {
if (response.success) {
this.dialogRef.close(true);
}
},
error: (error) => {
console.error('Error creating strategy:', error);
}
});
}
}
updateParameter(key: string, value: any): void {
this.parameters[key] = value;
}
}
import { Component, Inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import {
StrategyService,
TradingStrategy
} from '../../../services/strategy.service';
@Component({
selector: 'app-strategy-dialog',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatChipsModule,
MatIconModule,
MatAutocompleteModule
],
templateUrl: './strategy-dialog.component.html',
styleUrl: './strategy-dialog.component.css'
})
export class StrategyDialogComponent implements OnInit {
strategyForm: FormGroup;
isEditMode: boolean = false;
strategyTypes: string[] = [];
availableSymbols: string[] = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'SPY', 'QQQ'];
selectedSymbols: string[] = [];
separatorKeysCodes: number[] = [ENTER, COMMA];
parameters: Record<string, any> = {};
constructor(
private fb: FormBuilder,
private strategyService: StrategyService,
@Inject(MAT_DIALOG_DATA) public data: TradingStrategy | null,
private dialogRef: MatDialogRef<StrategyDialogComponent>
) {
this.isEditMode = !!data;
this.strategyForm = this.fb.group({
name: ['', [Validators.required]],
description: [''],
type: ['', [Validators.required]],
// Dynamic parameters will be added based on strategy type
});
if (this.isEditMode && data) {
this.selectedSymbols = [...data.symbols];
this.strategyForm.patchValue({
name: data.name,
description: data.description,
type: data.type
});
this.parameters = {...data.parameters};
}
}
ngOnInit(): void {
// In a real implementation, fetch available strategy types from the API
this.loadStrategyTypes();
}
loadStrategyTypes(): void {
// In a real implementation, this would call the API
this.strategyService.getStrategyTypes().subscribe({
next: (response) => {
if (response.success) {
this.strategyTypes = response.data;
// If editing, load parameters
if (this.isEditMode && this.data) {
this.onStrategyTypeChange(this.data.type);
}
}
},
error: (error) => {
console.error('Error loading strategy types:', error);
// Fallback to hardcoded types
this.strategyTypes = ['MOVING_AVERAGE_CROSSOVER', 'MEAN_REVERSION', 'CUSTOM'];
}
});
}
onStrategyTypeChange(type: string): void {
// Get default parameters for this strategy type
this.strategyService.getStrategyParameters(type).subscribe({
next: (response) => {
if (response.success) {
// If editing, merge default with existing
if (this.isEditMode && this.data) {
this.parameters = {
...response.data,
...this.data.parameters
};
} else {
this.parameters = response.data;
}
}
},
error: (error) => {
console.error('Error loading parameters:', error);
// Fallback to empty parameters
this.parameters = {};
}
});
}
addSymbol(symbol: string): void {
if (!symbol || this.selectedSymbols.includes(symbol)) return;
this.selectedSymbols.push(symbol);
}
removeSymbol(symbol: string): void {
this.selectedSymbols = this.selectedSymbols.filter(s => s !== symbol);
}
onSubmit(): void {
if (this.strategyForm.invalid || this.selectedSymbols.length === 0) {
return;
}
const formValue = this.strategyForm.value;
const strategy: Partial<TradingStrategy> = {
name: formValue.name,
description: formValue.description,
type: formValue.type,
symbols: this.selectedSymbols,
parameters: this.parameters,
};
if (this.isEditMode && this.data) {
this.strategyService.updateStrategy(this.data.id, strategy).subscribe({
next: (response) => {
if (response.success) {
this.dialogRef.close(true);
}
},
error: (error) => {
console.error('Error updating strategy:', error);
}
});
} else {
this.strategyService.createStrategy(strategy).subscribe({
next: (response) => {
if (response.success) {
this.dialogRef.close(true);
}
},
error: (error) => {
console.error('Error creating strategy:', error);
}
});
}
}
updateParameter(key: string, value: any): void {
this.parameters[key] = value;
}
}

View file

@ -1 +1 @@
/* Strategies specific styles */
/* Strategies specific styles */

View file

@ -1,142 +1,142 @@
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Trading Strategies</h1>
<p class="text-gray-600 mt-1">Configure and monitor your automated trading strategies</p>
</div>
<div class="flex gap-2">
<button mat-raised-button color="primary" (click)="openStrategyDialog()">
<mat-icon>add</mat-icon> New Strategy
</button>
<button mat-raised-button color="accent" (click)="openBacktestDialog()">
<mat-icon>science</mat-icon> New Backtest
</button>
</div>
</div>
<mat-card *ngIf="isLoading" class="p-4">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card>
<div *ngIf="!selectedStrategy; else strategyDetails">
<mat-card *ngIf="strategies.length > 0; else noStrategies" class="p-4">
<table mat-table [dataSource]="strategies" class="w-full">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Strategy</th>
<td mat-cell *matCellDef="let strategy">
<div class="font-semibold">{{strategy.name}}</div>
<div class="text-xs text-gray-500">{{strategy.description}}</div>
</td>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>Type</th>
<td mat-cell *matCellDef="let strategy">{{strategy.type}}</td>
</ng-container>
<!-- Symbols Column -->
<ng-container matColumnDef="symbols">
<th mat-header-cell *matHeaderCellDef>Symbols</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex flex-wrap gap-1 max-w-xs">
<mat-chip *ngFor="let symbol of strategy.symbols.slice(0, 3)">
{{symbol}}
</mat-chip>
<span *ngIf="strategy.symbols.length > 3" class="text-gray-500">
+{{strategy.symbols.length - 3}} more
</span>
</div>
</td>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full"
[style.background-color]="getStatusColor(strategy.status)"></span>
{{strategy.status}}
</div>
</td>
</ng-container>
<!-- Performance Column -->
<ng-container matColumnDef="performance">
<th mat-header-cell *matHeaderCellDef>Performance</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex flex-col">
<div class="flex justify-between">
<span class="text-xs text-gray-500">Return:</span>
<span [ngClass]="{'text-green-600': strategy.performance.totalReturn > 0,
'text-red-600': strategy.performance.totalReturn < 0}">
{{strategy.performance.totalReturn | percent:'1.2-2'}}
</span>
</div>
<div class="flex justify-between">
<span class="text-xs text-gray-500">Win Rate:</span>
<span>{{strategy.performance.winRate | percent:'1.0-0'}}</span>
</div>
</div>
</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex gap-2">
<button mat-icon-button color="primary" (click)="viewStrategyDetails(strategy)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button [color]="strategy.status === 'ACTIVE' ? 'warn' : 'primary'"
(click)="toggleStrategyStatus(strategy)">
<mat-icon>{{strategy.status === 'ACTIVE' ? 'pause' : 'play_arrow'}}</mat-icon>
</button>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="openStrategyDialog(strategy)">
<mat-icon>edit</mat-icon>
<span>Edit</span>
</button>
<button mat-menu-item (click)="openBacktestDialog(strategy)">
<mat-icon>science</mat-icon>
<span>Backtest</span>
</button>
</mat-menu>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</mat-card>
<ng-template #noStrategies>
<mat-card class="p-6 flex flex-col items-center justify-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem; margin: 0 auto;">psychology</mat-icon>
<h3 class="text-xl font-semibold mt-4">No Strategies Yet</h3>
<p class="mb-4">Create your first trading strategy to get started</p>
<button mat-raised-button color="primary" (click)="openStrategyDialog()">
<mat-icon>add</mat-icon> Create Strategy
</button>
</div>
</mat-card>
</ng-template>
</div>
<ng-template #strategyDetails>
<div class="flex justify-between items-center mb-4">
<button mat-button (click)="selectedStrategy = null">
<mat-icon>arrow_back</mat-icon> Back to Strategies
</button>
</div>
<app-strategy-details [strategy]="selectedStrategy"></app-strategy-details>
</ng-template>
</div>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Trading Strategies</h1>
<p class="text-gray-600 mt-1">Configure and monitor your automated trading strategies</p>
</div>
<div class="flex gap-2">
<button mat-raised-button color="primary" (click)="openStrategyDialog()">
<mat-icon>add</mat-icon> New Strategy
</button>
<button mat-raised-button color="accent" (click)="openBacktestDialog()">
<mat-icon>science</mat-icon> New Backtest
</button>
</div>
</div>
<mat-card *ngIf="isLoading" class="p-4">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card>
<div *ngIf="!selectedStrategy; else strategyDetails">
<mat-card *ngIf="strategies.length > 0; else noStrategies" class="p-4">
<table mat-table [dataSource]="strategies" class="w-full">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Strategy</th>
<td mat-cell *matCellDef="let strategy">
<div class="font-semibold">{{strategy.name}}</div>
<div class="text-xs text-gray-500">{{strategy.description}}</div>
</td>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>Type</th>
<td mat-cell *matCellDef="let strategy">{{strategy.type}}</td>
</ng-container>
<!-- Symbols Column -->
<ng-container matColumnDef="symbols">
<th mat-header-cell *matHeaderCellDef>Symbols</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex flex-wrap gap-1 max-w-xs">
<mat-chip *ngFor="let symbol of strategy.symbols.slice(0, 3)">
{{symbol}}
</mat-chip>
<span *ngIf="strategy.symbols.length > 3" class="text-gray-500">
+{{strategy.symbols.length - 3}} more
</span>
</div>
</td>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full"
[style.background-color]="getStatusColor(strategy.status)"></span>
{{strategy.status}}
</div>
</td>
</ng-container>
<!-- Performance Column -->
<ng-container matColumnDef="performance">
<th mat-header-cell *matHeaderCellDef>Performance</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex flex-col">
<div class="flex justify-between">
<span class="text-xs text-gray-500">Return:</span>
<span [ngClass]="{'text-green-600': strategy.performance.totalReturn > 0,
'text-red-600': strategy.performance.totalReturn < 0}">
{{strategy.performance.totalReturn | percent:'1.2-2'}}
</span>
</div>
<div class="flex justify-between">
<span class="text-xs text-gray-500">Win Rate:</span>
<span>{{strategy.performance.winRate | percent:'1.0-0'}}</span>
</div>
</div>
</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let strategy">
<div class="flex gap-2">
<button mat-icon-button color="primary" (click)="viewStrategyDetails(strategy)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button [color]="strategy.status === 'ACTIVE' ? 'warn' : 'primary'"
(click)="toggleStrategyStatus(strategy)">
<mat-icon>{{strategy.status === 'ACTIVE' ? 'pause' : 'play_arrow'}}</mat-icon>
</button>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="openStrategyDialog(strategy)">
<mat-icon>edit</mat-icon>
<span>Edit</span>
</button>
<button mat-menu-item (click)="openBacktestDialog(strategy)">
<mat-icon>science</mat-icon>
<span>Backtest</span>
</button>
</mat-menu>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</mat-card>
<ng-template #noStrategies>
<mat-card class="p-6 flex flex-col items-center justify-center">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem; margin: 0 auto;">psychology</mat-icon>
<h3 class="text-xl font-semibold mt-4">No Strategies Yet</h3>
<p class="mb-4">Create your first trading strategy to get started</p>
<button mat-raised-button color="primary" (click)="openStrategyDialog()">
<mat-icon>add</mat-icon> Create Strategy
</button>
</div>
</mat-card>
</ng-template>
</div>
<ng-template #strategyDetails>
<div class="flex justify-between items-center mb-4">
<button mat-button (click)="selectedStrategy = null">
<mat-icon>arrow_back</mat-icon> Back to Strategies
</button>
</div>
<app-strategy-details [strategy]="selectedStrategy"></app-strategy-details>
</ng-template>
</div>

View file

@ -1,148 +1,148 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatDialogModule, MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { MatChipsModule } from '@angular/material/chips';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StrategyService, TradingStrategy } from '../../services/strategy.service';
import { WebSocketService } from '../../services/websocket.service';
import { StrategyDialogComponent } from './dialogs/strategy-dialog.component';
import { BacktestDialogComponent } from './dialogs/backtest-dialog.component';
import { StrategyDetailsComponent } from './strategy-details/strategy-details.component';
@Component({
selector: 'app-strategies',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTabsModule,
MatTableModule,
MatSortModule,
MatPaginatorModule,
MatDialogModule,
MatMenuModule,
MatChipsModule,
MatProgressBarModule,
FormsModule,
ReactiveFormsModule,
StrategyDetailsComponent
],
templateUrl: './strategies.component.html',
styleUrl: './strategies.component.css'
})
export class StrategiesComponent implements OnInit {
strategies: TradingStrategy[] = [];
displayedColumns: string[] = ['name', 'type', 'symbols', 'status', 'performance', 'actions'];
selectedStrategy: TradingStrategy | null = null;
isLoading = false;
constructor(
private strategyService: StrategyService,
private webSocketService: WebSocketService,
private dialog: MatDialog
) {}
ngOnInit(): void {
this.loadStrategies();
this.listenForStrategyUpdates();
}
loadStrategies(): void {
this.isLoading = true;
this.strategyService.getStrategies().subscribe({
next: (response) => {
if (response.success) {
this.strategies = response.data;
}
this.isLoading = false;
},
error: (error) => {
console.error('Error loading strategies:', error);
this.isLoading = false;
}
});
}
listenForStrategyUpdates(): void {
this.webSocketService.messages.subscribe(message => {
if (message.type === 'STRATEGY_CREATED' ||
message.type === 'STRATEGY_UPDATED' ||
message.type === 'STRATEGY_STATUS_CHANGED') {
// Refresh the strategy list when changes occur
this.loadStrategies();
}
});
}
getStatusColor(status: string): string {
switch (status) {
case 'ACTIVE': return 'green';
case 'PAUSED': return 'orange';
case 'ERROR': return 'red';
default: return 'gray';
}
}
openStrategyDialog(strategy?: TradingStrategy): void {
const dialogRef = this.dialog.open(StrategyDialogComponent, {
width: '600px',
data: strategy || null
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadStrategies();
}
});
}
openBacktestDialog(strategy?: TradingStrategy): void {
const dialogRef = this.dialog.open(BacktestDialogComponent, {
width: '800px',
data: strategy || null
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Handle backtest result if needed
}
});
}
toggleStrategyStatus(strategy: TradingStrategy): void {
this.isLoading = true;
if (strategy.status === 'ACTIVE') {
this.strategyService.pauseStrategy(strategy.id).subscribe({
next: () => this.loadStrategies(),
error: (error) => {
console.error('Error pausing strategy:', error);
this.isLoading = false;
}
});
} else {
this.strategyService.startStrategy(strategy.id).subscribe({
next: () => this.loadStrategies(),
error: (error) => {
console.error('Error starting strategy:', error);
this.isLoading = false;
}
});
}
}
viewStrategyDetails(strategy: TradingStrategy): void {
this.selectedStrategy = strategy;
}
}
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatDialogModule, MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { MatChipsModule } from '@angular/material/chips';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StrategyService, TradingStrategy } from '../../services/strategy.service';
import { WebSocketService } from '../../services/websocket.service';
import { StrategyDialogComponent } from './dialogs/strategy-dialog.component';
import { BacktestDialogComponent } from './dialogs/backtest-dialog.component';
import { StrategyDetailsComponent } from './strategy-details/strategy-details.component';
@Component({
selector: 'app-strategies',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTabsModule,
MatTableModule,
MatSortModule,
MatPaginatorModule,
MatDialogModule,
MatMenuModule,
MatChipsModule,
MatProgressBarModule,
FormsModule,
ReactiveFormsModule,
StrategyDetailsComponent
],
templateUrl: './strategies.component.html',
styleUrl: './strategies.component.css'
})
export class StrategiesComponent implements OnInit {
strategies: TradingStrategy[] = [];
displayedColumns: string[] = ['name', 'type', 'symbols', 'status', 'performance', 'actions'];
selectedStrategy: TradingStrategy | null = null;
isLoading = false;
constructor(
private strategyService: StrategyService,
private webSocketService: WebSocketService,
private dialog: MatDialog
) {}
ngOnInit(): void {
this.loadStrategies();
this.listenForStrategyUpdates();
}
loadStrategies(): void {
this.isLoading = true;
this.strategyService.getStrategies().subscribe({
next: (response) => {
if (response.success) {
this.strategies = response.data;
}
this.isLoading = false;
},
error: (error) => {
console.error('Error loading strategies:', error);
this.isLoading = false;
}
});
}
listenForStrategyUpdates(): void {
this.webSocketService.messages.subscribe(message => {
if (message.type === 'STRATEGY_CREATED' ||
message.type === 'STRATEGY_UPDATED' ||
message.type === 'STRATEGY_STATUS_CHANGED') {
// Refresh the strategy list when changes occur
this.loadStrategies();
}
});
}
getStatusColor(status: string): string {
switch (status) {
case 'ACTIVE': return 'green';
case 'PAUSED': return 'orange';
case 'ERROR': return 'red';
default: return 'gray';
}
}
openStrategyDialog(strategy?: TradingStrategy): void {
const dialogRef = this.dialog.open(StrategyDialogComponent, {
width: '600px',
data: strategy || null
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadStrategies();
}
});
}
openBacktestDialog(strategy?: TradingStrategy): void {
const dialogRef = this.dialog.open(BacktestDialogComponent, {
width: '800px',
data: strategy || null
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Handle backtest result if needed
}
});
}
toggleStrategyStatus(strategy: TradingStrategy): void {
this.isLoading = true;
if (strategy.status === 'ACTIVE') {
this.strategyService.pauseStrategy(strategy.id).subscribe({
next: () => this.loadStrategies(),
error: (error) => {
console.error('Error pausing strategy:', error);
this.isLoading = false;
}
});
} else {
this.strategyService.startStrategy(strategy.id).subscribe({
next: () => this.loadStrategies(),
error: (error) => {
console.error('Error starting strategy:', error);
this.isLoading = false;
}
});
}
}
viewStrategyDetails(strategy: TradingStrategy): void {
this.selectedStrategy = strategy;
}
}

View file

@ -1,16 +1,16 @@
/* Strategy details specific styles */
table {
width: 100%;
border-collapse: collapse;
}
th {
font-weight: 600;
color: #4b5563;
font-size: 0.875rem;
border-bottom: 1px solid #e5e7eb;
}
td {
border-bottom: 1px solid #e5e7eb;
}
/* Strategy details specific styles */
table {
width: 100%;
border-collapse: collapse;
}
th {
font-weight: 600;
color: #4b5563;
font-size: 0.875rem;
border-bottom: 1px solid #e5e7eb;
}
td {
border-bottom: 1px solid #e5e7eb;
}

View file

@ -1,214 +1,214 @@
<div class="space-y-6" *ngIf="strategy">
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<!-- Strategy Overview Card -->
<mat-card class="flex-1 p-4">
<div class="flex items-start justify-between">
<div>
<h2 class="text-xl font-bold">{{strategy.name}}</h2>
<p class="text-gray-600 text-sm">{{strategy.description}}</p>
</div>
<div class="flex items-center gap-2">
<button mat-raised-button color="primary" class="mr-2" (click)="openBacktestDialog()">
Run Backtest
</button>
<span class="px-3 py-1 rounded-full text-xs font-semibold"
[style.background-color]="getStatusColor(strategy.status)"
style="color: white;">
{{strategy.status}}
</span>
</div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h3 class="font-semibold text-sm text-gray-600">Type</h3>
<p>{{strategy.type}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Created</h3>
<p>{{strategy.createdAt | date:'medium'}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Last Updated</h3>
<p>{{strategy.updatedAt | date:'medium'}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Symbols</h3>
<div class="flex flex-wrap gap-1 mt-1">
<mat-chip *ngFor="let symbol of strategy.symbols">{{symbol}}</mat-chip>
</div>
</div>
</div>
</mat-card>
<!-- Performance Summary Card -->
<mat-card class="md:w-1/3 p-4">
<h3 class="text-lg font-bold mb-3">Performance</h3>
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-sm text-gray-600">Return</p>
<p class="text-xl font-semibold"
[ngClass]="{'text-green-600': performance.totalReturn >= 0, 'text-red-600': performance.totalReturn < 0}">
{{performance.totalReturn | percent:'1.2-2'}}
</p>
</div>
<div>
<p class="text-sm text-gray-600">Win Rate</p>
<p class="text-xl font-semibold">{{performance.winRate | percent:'1.0-0'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Sharpe Ratio</p>
<p class="text-xl font-semibold">{{performance.sharpeRatio | number:'1.2-2'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Max Drawdown</p>
<p class="text-xl font-semibold text-red-600">{{performance.maxDrawdown | percent:'1.2-2'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Total Trades</p>
<p class="text-xl font-semibold">{{performance.totalTrades}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Sortino Ratio</p>
<p class="text-xl font-semibold">{{performance.sortinoRatio | number:'1.2-2'}}</p>
</div>
</div>
<mat-divider class="my-4"></mat-divider>
<div class="flex justify-between mt-2">
<button mat-button color="primary" *ngIf="strategy.status !== 'ACTIVE'" (click)="activateStrategy()">
<mat-icon>play_arrow</mat-icon> Start
</button>
<button mat-button color="accent" *ngIf="strategy.status === 'ACTIVE'" (click)="pauseStrategy()">
<mat-icon>pause</mat-icon> Pause
</button>
<button mat-button color="warn" *ngIf="strategy.status === 'ACTIVE'" (click)="stopStrategy()">
<mat-icon>stop</mat-icon> Stop
</button>
<button mat-button (click)="openEditDialog()">
<mat-icon>edit</mat-icon> Edit
</button>
</div>
</mat-card>
</div>
<!-- Parameters Card -->
<mat-card class="p-4">
<h3 class="text-lg font-bold mb-3">Strategy Parameters</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div *ngFor="let param of strategy.parameters | keyvalue">
<p class="text-sm text-gray-600">{{param.key}}</p>
<p class="font-semibold">{{param.value}}</p>
</div>
</div>
</mat-card>
<!-- Backtest Results Section (only shown when a backtest has been run) -->
<div *ngIf="backtestResult" class="backtest-results space-y-6">
<h2 class="text-xl font-bold">Backtest Results</h2>
<!-- Performance Metrics Component -->
<app-performance-metrics [backtestResult]="backtestResult"></app-performance-metrics>
<!-- Equity Chart Component -->
<app-equity-chart [backtestResult]="backtestResult"></app-equity-chart>
<!-- Drawdown Chart Component -->
<app-drawdown-chart [backtestResult]="backtestResult"></app-drawdown-chart>
<!-- Trades Table Component -->
<app-trades-table [backtestResult]="backtestResult"></app-trades-table>
</div>
<!-- Tabs for Signals/Trades -->
<mat-card class="p-0">
<mat-tab-group>
<!-- Signals Tab -->
<mat-tab label="Recent Signals">
<div class="p-4">
<ng-container *ngIf="!isLoadingSignals; else loadingSignals">
<table class="min-w-full">
<thead>
<tr>
<th class="py-2 text-left">Time</th>
<th class="py-2 text-left">Symbol</th>
<th class="py-2 text-left">Action</th>
<th class="py-2 text-left">Price</th>
<th class="py-2 text-left">Quantity</th>
<th class="py-2 text-left">Confidence</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let signal of signals">
<td class="py-2">{{signal.timestamp | date:'short'}}</td>
<td class="py-2">{{signal.symbol}}</td>
<td class="py-2">
<span class="px-2 py-1 rounded text-xs font-semibold"
[style.background-color]="getSignalColor(signal.action)"
style="color: white;">
{{signal.action}}
</span>
</td>
<td class="py-2">${{signal.price | number:'1.2-2'}}</td>
<td class="py-2">{{signal.quantity}}</td>
<td class="py-2">{{signal.confidence | percent:'1.0-0'}}</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #loadingSignals>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
</div>
</mat-tab>
<!-- Trades Tab -->
<mat-tab label="Recent Trades">
<div class="p-4">
<ng-container *ngIf="!isLoadingTrades; else loadingTrades">
<table class="min-w-full">
<thead>
<tr>
<th class="py-2 text-left">Symbol</th>
<th class="py-2 text-left">Entry</th>
<th class="py-2 text-left">Exit</th>
<th class="py-2 text-left">Quantity</th>
<th class="py-2 text-left">P&L</th>
<th class="py-2 text-left">P&L %</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let trade of trades">
<td class="py-2">{{trade.symbol}}</td>
<td class="py-2">
${{trade.entryPrice | number:'1.2-2'}} @ {{trade.entryTime | date:'short'}}
</td>
<td class="py-2">
${{trade.exitPrice | number:'1.2-2'}} @ {{trade.exitTime | date:'short'}}
</td>
<td class="py-2">{{trade.quantity}}</td>
<td class="py-2" [ngClass]="{'text-green-600': trade.pnl >= 0, 'text-red-600': trade.pnl < 0}">
${{trade.pnl | number:'1.2-2'}}
</td>
<td class="py-2" [ngClass]="{'text-green-600': trade.pnlPercent >= 0, 'text-red-600': trade.pnlPercent < 0}">
{{trade.pnlPercent | number:'1.2-2'}}%
</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #loadingTrades>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
</div>
</mat-tab>
</mat-tab-group>
</mat-card>
</div>
<mat-card class="p-6 flex items-center" *ngIf="!strategy">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">psychology</mat-icon>
<p class="mb-4">No strategy selected</p>
</div>
</mat-card>
<div class="space-y-6" *ngIf="strategy">
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<!-- Strategy Overview Card -->
<mat-card class="flex-1 p-4">
<div class="flex items-start justify-between">
<div>
<h2 class="text-xl font-bold">{{strategy.name}}</h2>
<p class="text-gray-600 text-sm">{{strategy.description}}</p>
</div>
<div class="flex items-center gap-2">
<button mat-raised-button color="primary" class="mr-2" (click)="openBacktestDialog()">
Run Backtest
</button>
<span class="px-3 py-1 rounded-full text-xs font-semibold"
[style.background-color]="getStatusColor(strategy.status)"
style="color: white;">
{{strategy.status}}
</span>
</div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h3 class="font-semibold text-sm text-gray-600">Type</h3>
<p>{{strategy.type}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Created</h3>
<p>{{strategy.createdAt | date:'medium'}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Last Updated</h3>
<p>{{strategy.updatedAt | date:'medium'}}</p>
</div>
<div>
<h3 class="font-semibold text-sm text-gray-600">Symbols</h3>
<div class="flex flex-wrap gap-1 mt-1">
<mat-chip *ngFor="let symbol of strategy.symbols">{{symbol}}</mat-chip>
</div>
</div>
</div>
</mat-card>
<!-- Performance Summary Card -->
<mat-card class="md:w-1/3 p-4">
<h3 class="text-lg font-bold mb-3">Performance</h3>
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-sm text-gray-600">Return</p>
<p class="text-xl font-semibold"
[ngClass]="{'text-green-600': performance.totalReturn >= 0, 'text-red-600': performance.totalReturn < 0}">
{{performance.totalReturn | percent:'1.2-2'}}
</p>
</div>
<div>
<p class="text-sm text-gray-600">Win Rate</p>
<p class="text-xl font-semibold">{{performance.winRate | percent:'1.0-0'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Sharpe Ratio</p>
<p class="text-xl font-semibold">{{performance.sharpeRatio | number:'1.2-2'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Max Drawdown</p>
<p class="text-xl font-semibold text-red-600">{{performance.maxDrawdown | percent:'1.2-2'}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Total Trades</p>
<p class="text-xl font-semibold">{{performance.totalTrades}}</p>
</div>
<div>
<p class="text-sm text-gray-600">Sortino Ratio</p>
<p class="text-xl font-semibold">{{performance.sortinoRatio | number:'1.2-2'}}</p>
</div>
</div>
<mat-divider class="my-4"></mat-divider>
<div class="flex justify-between mt-2">
<button mat-button color="primary" *ngIf="strategy.status !== 'ACTIVE'" (click)="activateStrategy()">
<mat-icon>play_arrow</mat-icon> Start
</button>
<button mat-button color="accent" *ngIf="strategy.status === 'ACTIVE'" (click)="pauseStrategy()">
<mat-icon>pause</mat-icon> Pause
</button>
<button mat-button color="warn" *ngIf="strategy.status === 'ACTIVE'" (click)="stopStrategy()">
<mat-icon>stop</mat-icon> Stop
</button>
<button mat-button (click)="openEditDialog()">
<mat-icon>edit</mat-icon> Edit
</button>
</div>
</mat-card>
</div>
<!-- Parameters Card -->
<mat-card class="p-4">
<h3 class="text-lg font-bold mb-3">Strategy Parameters</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div *ngFor="let param of strategy.parameters | keyvalue">
<p class="text-sm text-gray-600">{{param.key}}</p>
<p class="font-semibold">{{param.value}}</p>
</div>
</div>
</mat-card>
<!-- Backtest Results Section (only shown when a backtest has been run) -->
<div *ngIf="backtestResult" class="backtest-results space-y-6">
<h2 class="text-xl font-bold">Backtest Results</h2>
<!-- Performance Metrics Component -->
<app-performance-metrics [backtestResult]="backtestResult"></app-performance-metrics>
<!-- Equity Chart Component -->
<app-equity-chart [backtestResult]="backtestResult"></app-equity-chart>
<!-- Drawdown Chart Component -->
<app-drawdown-chart [backtestResult]="backtestResult"></app-drawdown-chart>
<!-- Trades Table Component -->
<app-trades-table [backtestResult]="backtestResult"></app-trades-table>
</div>
<!-- Tabs for Signals/Trades -->
<mat-card class="p-0">
<mat-tab-group>
<!-- Signals Tab -->
<mat-tab label="Recent Signals">
<div class="p-4">
<ng-container *ngIf="!isLoadingSignals; else loadingSignals">
<table class="min-w-full">
<thead>
<tr>
<th class="py-2 text-left">Time</th>
<th class="py-2 text-left">Symbol</th>
<th class="py-2 text-left">Action</th>
<th class="py-2 text-left">Price</th>
<th class="py-2 text-left">Quantity</th>
<th class="py-2 text-left">Confidence</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let signal of signals">
<td class="py-2">{{signal.timestamp | date:'short'}}</td>
<td class="py-2">{{signal.symbol}}</td>
<td class="py-2">
<span class="px-2 py-1 rounded text-xs font-semibold"
[style.background-color]="getSignalColor(signal.action)"
style="color: white;">
{{signal.action}}
</span>
</td>
<td class="py-2">${{signal.price | number:'1.2-2'}}</td>
<td class="py-2">{{signal.quantity}}</td>
<td class="py-2">{{signal.confidence | percent:'1.0-0'}}</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #loadingSignals>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
</div>
</mat-tab>
<!-- Trades Tab -->
<mat-tab label="Recent Trades">
<div class="p-4">
<ng-container *ngIf="!isLoadingTrades; else loadingTrades">
<table class="min-w-full">
<thead>
<tr>
<th class="py-2 text-left">Symbol</th>
<th class="py-2 text-left">Entry</th>
<th class="py-2 text-left">Exit</th>
<th class="py-2 text-left">Quantity</th>
<th class="py-2 text-left">P&L</th>
<th class="py-2 text-left">P&L %</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let trade of trades">
<td class="py-2">{{trade.symbol}}</td>
<td class="py-2">
${{trade.entryPrice | number:'1.2-2'}} @ {{trade.entryTime | date:'short'}}
</td>
<td class="py-2">
${{trade.exitPrice | number:'1.2-2'}} @ {{trade.exitTime | date:'short'}}
</td>
<td class="py-2">{{trade.quantity}}</td>
<td class="py-2" [ngClass]="{'text-green-600': trade.pnl >= 0, 'text-red-600': trade.pnl < 0}">
${{trade.pnl | number:'1.2-2'}}
</td>
<td class="py-2" [ngClass]="{'text-green-600': trade.pnlPercent >= 0, 'text-red-600': trade.pnlPercent < 0}">
{{trade.pnlPercent | number:'1.2-2'}}%
</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #loadingTrades>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
</div>
</mat-tab>
</mat-tab-group>
</mat-card>
</div>
<mat-card class="p-6 flex items-center" *ngIf="!strategy">
<div class="text-center text-gray-500 w-full">
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">psychology</mat-icon>
<p class="mb-4">No strategy selected</p>
</div>
</mat-card>

View file

@ -1,381 +1,381 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatChipsModule } from '@angular/material/chips';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatDividerModule } from '@angular/material/divider';
import { MatDialog } from '@angular/material/dialog';
import { BacktestResult, TradingStrategy, StrategyService } from '../../../services/strategy.service';
import { WebSocketService } from '../../../services/websocket.service';
import { EquityChartComponent } from '../components/equity-chart.component';
import { DrawdownChartComponent } from '../components/drawdown-chart.component';
import { TradesTableComponent } from '../components/trades-table.component';
import { PerformanceMetricsComponent } from '../components/performance-metrics.component';
import { StrategyDialogComponent } from '../dialogs/strategy-dialog.component';
import { BacktestDialogComponent } from '../dialogs/backtest-dialog.component';
@Component({
selector: 'app-strategy-details',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatTabsModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatChipsModule,
MatProgressBarModule,
MatDividerModule,
EquityChartComponent,
DrawdownChartComponent,
TradesTableComponent,
PerformanceMetricsComponent
],
templateUrl: './strategy-details.component.html',
styleUrl: './strategy-details.component.css'
})
export class StrategyDetailsComponent implements OnChanges {
@Input() strategy: TradingStrategy | null = null;
signals: any[] = [];
trades: any[] = [];
performance: any = {};
isLoadingSignals = false;
isLoadingTrades = false;
backtestResult: BacktestResult | undefined;
constructor(
private strategyService: StrategyService,
private webSocketService: WebSocketService,
private dialog: MatDialog
) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes['strategy'] && this.strategy) {
this.loadStrategyData();
this.listenForUpdates();
}
}
loadStrategyData(): void {
if (!this.strategy) return;
// In a real implementation, these would call API methods to fetch the data
this.loadSignals();
this.loadTrades();
this.loadPerformance();
}
loadSignals(): void {
if (!this.strategy) return;
this.isLoadingSignals = true;
// First check if we can get real signals from the API
this.strategyService.getStrategySignals(this.strategy.id)
.subscribe({
next: (response) => {
if (response.success && response.data && response.data.length > 0) {
this.signals = response.data;
} else {
// Fallback to mock data if no real signals available
this.signals = this.generateMockSignals();
}
this.isLoadingSignals = false;
},
error: (error) => {
console.error('Error loading signals', error);
// Fallback to mock data on error
this.signals = this.generateMockSignals();
this.isLoadingSignals = false;
}
});
}
loadTrades(): void {
if (!this.strategy) return;
this.isLoadingTrades = true;
// First check if we can get real trades from the API
this.strategyService.getStrategyTrades(this.strategy.id)
.subscribe({
next: (response) => {
if (response.success && response.data && response.data.length > 0) {
this.trades = response.data;
} else {
// Fallback to mock data if no real trades available
this.trades = this.generateMockTrades();
}
this.isLoadingTrades = false;
},
error: (error) => {
console.error('Error loading trades', error);
// Fallback to mock data on error
this.trades = this.generateMockTrades();
this.isLoadingTrades = false;
}
});
}
loadPerformance(): void {
// This would be an API call in a real implementation
this.performance = {
totalReturn: this.strategy?.performance.totalReturn || 0,
winRate: this.strategy?.performance.winRate || 0,
sharpeRatio: this.strategy?.performance.sharpeRatio || 0,
maxDrawdown: this.strategy?.performance.maxDrawdown || 0,
totalTrades: this.strategy?.performance.totalTrades || 0,
// Additional metrics that would come from the API
dailyReturn: 0.0012,
volatility: 0.008,
sortinoRatio: 1.2,
calmarRatio: 0.7
};
}
listenForUpdates(): void {
if (!this.strategy) return;
// Subscribe to strategy signals
this.webSocketService.getStrategySignals(this.strategy.id)
.subscribe((signal: any) => {
// Add the new signal to the top of the list
this.signals = [signal, ...this.signals.slice(0, 9)]; // Keep only the latest 10 signals
});
// Subscribe to strategy trades
this.webSocketService.getStrategyTrades(this.strategy.id)
.subscribe((trade: any) => {
// Add the new trade to the top of the list
this.trades = [trade, ...this.trades.slice(0, 9)]; // Keep only the latest 10 trades
// Update performance metrics
this.updatePerformanceMetrics();
});
// Subscribe to strategy status updates
this.webSocketService.getStrategyUpdates()
.subscribe((update: any) => {
if (update.strategyId === this.strategy?.id) {
// Update strategy status if changed
if (update.status && this.strategy && this.strategy.status !== update.status) {
this.strategy.status = update.status;
}
// Update other fields if present
if (update.performance && this.strategy) {
this.strategy.performance = {
...this.strategy.performance,
...update.performance
};
this.performance = {
...this.performance,
...update.performance
};
}
}
});
console.log('WebSocket listeners for strategy updates initialized');
}
/**
* Update performance metrics when new trades come in
*/
private updatePerformanceMetrics(): void {
if (!this.strategy || this.trades.length === 0) return;
// Calculate basic metrics
const winningTrades = this.trades.filter(t => t.pnl > 0);
const losingTrades = this.trades.filter(t => t.pnl < 0);
const totalPnl = this.trades.reduce((sum, trade) => sum + trade.pnl, 0);
const winRate = winningTrades.length / this.trades.length;
// Update performance data
const currentPerformance = this.performance || {};
this.performance = {
...currentPerformance,
totalTrades: this.trades.length,
winRate: winRate,
totalReturn: (currentPerformance.totalReturn || 0) + (totalPnl / 10000) // Approximate
};
// Update strategy performance as well
if (this.strategy && this.strategy.performance) {
this.strategy.performance = {
...this.strategy.performance,
totalTrades: this.trades.length,
winRate: winRate
};
}
}
getStatusColor(status: string): string {
switch (status) {
case 'ACTIVE': return 'green';
case 'PAUSED': return 'orange';
case 'ERROR': return 'red';
default: return 'gray';
}
}
getSignalColor(action: string): string {
switch (action) {
case 'BUY': return 'green';
case 'SELL': return 'red';
default: return 'gray';
}
}
/**
* Open the backtest dialog to run a backtest for this strategy
*/
openBacktestDialog(): void {
if (!this.strategy) return;
const dialogRef = this.dialog.open(BacktestDialogComponent, {
width: '800px',
data: this.strategy
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Store the backtest result for visualization
this.backtestResult = result;
}
});
}
/**
* Open the strategy edit dialog
*/
openEditDialog(): void {
if (!this.strategy) return;
const dialogRef = this.dialog.open(StrategyDialogComponent, {
width: '600px',
data: this.strategy
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Refresh strategy data after edit
this.loadStrategyData();
}
});
}
/**
* Start the strategy
*/
activateStrategy(): void {
if (!this.strategy) return;
this.strategyService.startStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'ACTIVE';
}
},
error: (error) => {
console.error('Error starting strategy:', error);
}
});
}
/**
* Pause the strategy
*/
pauseStrategy(): void {
if (!this.strategy) return;
this.strategyService.pauseStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'PAUSED';
}
},
error: (error) => {
console.error('Error pausing strategy:', error);
}
});
}
/**
* Stop the strategy
*/
stopStrategy(): void {
if (!this.strategy) return;
this.strategyService.stopStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'INACTIVE';
}
},
error: (error) => {
console.error('Error stopping strategy:', error);
}
});
}
// Methods to generate mock data
private generateMockSignals(): any[] {
if (!this.strategy) return [];
const signals = [];
const actions = ['BUY', 'SELL', 'HOLD'];
const now = new Date();
for (let i = 0; i < 10; i++) {
const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)];
const action = actions[Math.floor(Math.random() * actions.length)];
signals.push({
id: `sig_${i}`,
symbol,
action,
confidence: 0.7 + Math.random() * 0.3,
price: 100 + Math.random() * 50,
timestamp: new Date(now.getTime() - i * 1000 * 60 * 30), // 30 min intervals
quantity: Math.floor(10 + Math.random() * 90)
});
}
return signals;
}
private generateMockTrades(): any[] {
if (!this.strategy) return [];
const trades = [];
const now = new Date();
for (let i = 0; i < 10; i++) {
const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)];
const entryPrice = 100 + Math.random() * 50;
const exitPrice = entryPrice * (1 + (Math.random() * 0.1 - 0.05)); // -5% to +5%
const quantity = Math.floor(10 + Math.random() * 90);
const pnl = (exitPrice - entryPrice) * quantity;
trades.push({
id: `trade_${i}`,
symbol,
entryPrice,
entryTime: new Date(now.getTime() - (i + 5) * 1000 * 60 * 60), // Hourly intervals
exitPrice,
exitTime: new Date(now.getTime() - i * 1000 * 60 * 60),
quantity,
pnl,
pnlPercent: ((exitPrice - entryPrice) / entryPrice) * 100
});
}
return trades;
}
}
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatChipsModule } from '@angular/material/chips';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatDividerModule } from '@angular/material/divider';
import { MatDialog } from '@angular/material/dialog';
import { BacktestResult, TradingStrategy, StrategyService } from '../../../services/strategy.service';
import { WebSocketService } from '../../../services/websocket.service';
import { EquityChartComponent } from '../components/equity-chart.component';
import { DrawdownChartComponent } from '../components/drawdown-chart.component';
import { TradesTableComponent } from '../components/trades-table.component';
import { PerformanceMetricsComponent } from '../components/performance-metrics.component';
import { StrategyDialogComponent } from '../dialogs/strategy-dialog.component';
import { BacktestDialogComponent } from '../dialogs/backtest-dialog.component';
@Component({
selector: 'app-strategy-details',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatTabsModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatChipsModule,
MatProgressBarModule,
MatDividerModule,
EquityChartComponent,
DrawdownChartComponent,
TradesTableComponent,
PerformanceMetricsComponent
],
templateUrl: './strategy-details.component.html',
styleUrl: './strategy-details.component.css'
})
export class StrategyDetailsComponent implements OnChanges {
@Input() strategy: TradingStrategy | null = null;
signals: any[] = [];
trades: any[] = [];
performance: any = {};
isLoadingSignals = false;
isLoadingTrades = false;
backtestResult: BacktestResult | undefined;
constructor(
private strategyService: StrategyService,
private webSocketService: WebSocketService,
private dialog: MatDialog
) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes['strategy'] && this.strategy) {
this.loadStrategyData();
this.listenForUpdates();
}
}
loadStrategyData(): void {
if (!this.strategy) return;
// In a real implementation, these would call API methods to fetch the data
this.loadSignals();
this.loadTrades();
this.loadPerformance();
}
loadSignals(): void {
if (!this.strategy) return;
this.isLoadingSignals = true;
// First check if we can get real signals from the API
this.strategyService.getStrategySignals(this.strategy.id)
.subscribe({
next: (response) => {
if (response.success && response.data && response.data.length > 0) {
this.signals = response.data;
} else {
// Fallback to mock data if no real signals available
this.signals = this.generateMockSignals();
}
this.isLoadingSignals = false;
},
error: (error) => {
console.error('Error loading signals', error);
// Fallback to mock data on error
this.signals = this.generateMockSignals();
this.isLoadingSignals = false;
}
});
}
loadTrades(): void {
if (!this.strategy) return;
this.isLoadingTrades = true;
// First check if we can get real trades from the API
this.strategyService.getStrategyTrades(this.strategy.id)
.subscribe({
next: (response) => {
if (response.success && response.data && response.data.length > 0) {
this.trades = response.data;
} else {
// Fallback to mock data if no real trades available
this.trades = this.generateMockTrades();
}
this.isLoadingTrades = false;
},
error: (error) => {
console.error('Error loading trades', error);
// Fallback to mock data on error
this.trades = this.generateMockTrades();
this.isLoadingTrades = false;
}
});
}
loadPerformance(): void {
// This would be an API call in a real implementation
this.performance = {
totalReturn: this.strategy?.performance.totalReturn || 0,
winRate: this.strategy?.performance.winRate || 0,
sharpeRatio: this.strategy?.performance.sharpeRatio || 0,
maxDrawdown: this.strategy?.performance.maxDrawdown || 0,
totalTrades: this.strategy?.performance.totalTrades || 0,
// Additional metrics that would come from the API
dailyReturn: 0.0012,
volatility: 0.008,
sortinoRatio: 1.2,
calmarRatio: 0.7
};
}
listenForUpdates(): void {
if (!this.strategy) return;
// Subscribe to strategy signals
this.webSocketService.getStrategySignals(this.strategy.id)
.subscribe((signal: any) => {
// Add the new signal to the top of the list
this.signals = [signal, ...this.signals.slice(0, 9)]; // Keep only the latest 10 signals
});
// Subscribe to strategy trades
this.webSocketService.getStrategyTrades(this.strategy.id)
.subscribe((trade: any) => {
// Add the new trade to the top of the list
this.trades = [trade, ...this.trades.slice(0, 9)]; // Keep only the latest 10 trades
// Update performance metrics
this.updatePerformanceMetrics();
});
// Subscribe to strategy status updates
this.webSocketService.getStrategyUpdates()
.subscribe((update: any) => {
if (update.strategyId === this.strategy?.id) {
// Update strategy status if changed
if (update.status && this.strategy && this.strategy.status !== update.status) {
this.strategy.status = update.status;
}
// Update other fields if present
if (update.performance && this.strategy) {
this.strategy.performance = {
...this.strategy.performance,
...update.performance
};
this.performance = {
...this.performance,
...update.performance
};
}
}
});
console.log('WebSocket listeners for strategy updates initialized');
}
/**
* Update performance metrics when new trades come in
*/
private updatePerformanceMetrics(): void {
if (!this.strategy || this.trades.length === 0) return;
// Calculate basic metrics
const winningTrades = this.trades.filter(t => t.pnl > 0);
const losingTrades = this.trades.filter(t => t.pnl < 0);
const totalPnl = this.trades.reduce((sum, trade) => sum + trade.pnl, 0);
const winRate = winningTrades.length / this.trades.length;
// Update performance data
const currentPerformance = this.performance || {};
this.performance = {
...currentPerformance,
totalTrades: this.trades.length,
winRate: winRate,
totalReturn: (currentPerformance.totalReturn || 0) + (totalPnl / 10000) // Approximate
};
// Update strategy performance as well
if (this.strategy && this.strategy.performance) {
this.strategy.performance = {
...this.strategy.performance,
totalTrades: this.trades.length,
winRate: winRate
};
}
}
getStatusColor(status: string): string {
switch (status) {
case 'ACTIVE': return 'green';
case 'PAUSED': return 'orange';
case 'ERROR': return 'red';
default: return 'gray';
}
}
getSignalColor(action: string): string {
switch (action) {
case 'BUY': return 'green';
case 'SELL': return 'red';
default: return 'gray';
}
}
/**
* Open the backtest dialog to run a backtest for this strategy
*/
openBacktestDialog(): void {
if (!this.strategy) return;
const dialogRef = this.dialog.open(BacktestDialogComponent, {
width: '800px',
data: this.strategy
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Store the backtest result for visualization
this.backtestResult = result;
}
});
}
/**
* Open the strategy edit dialog
*/
openEditDialog(): void {
if (!this.strategy) return;
const dialogRef = this.dialog.open(StrategyDialogComponent, {
width: '600px',
data: this.strategy
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Refresh strategy data after edit
this.loadStrategyData();
}
});
}
/**
* Start the strategy
*/
activateStrategy(): void {
if (!this.strategy) return;
this.strategyService.startStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'ACTIVE';
}
},
error: (error) => {
console.error('Error starting strategy:', error);
}
});
}
/**
* Pause the strategy
*/
pauseStrategy(): void {
if (!this.strategy) return;
this.strategyService.pauseStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'PAUSED';
}
},
error: (error) => {
console.error('Error pausing strategy:', error);
}
});
}
/**
* Stop the strategy
*/
stopStrategy(): void {
if (!this.strategy) return;
this.strategyService.stopStrategy(this.strategy.id).subscribe({
next: (response) => {
if (response.success) {
this.strategy!.status = 'INACTIVE';
}
},
error: (error) => {
console.error('Error stopping strategy:', error);
}
});
}
// Methods to generate mock data
private generateMockSignals(): any[] {
if (!this.strategy) return [];
const signals = [];
const actions = ['BUY', 'SELL', 'HOLD'];
const now = new Date();
for (let i = 0; i < 10; i++) {
const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)];
const action = actions[Math.floor(Math.random() * actions.length)];
signals.push({
id: `sig_${i}`,
symbol,
action,
confidence: 0.7 + Math.random() * 0.3,
price: 100 + Math.random() * 50,
timestamp: new Date(now.getTime() - i * 1000 * 60 * 30), // 30 min intervals
quantity: Math.floor(10 + Math.random() * 90)
});
}
return signals;
}
private generateMockTrades(): any[] {
if (!this.strategy) return [];
const trades = [];
const now = new Date();
for (let i = 0; i < 10; i++) {
const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)];
const entryPrice = 100 + Math.random() * 50;
const exitPrice = entryPrice * (1 + (Math.random() * 0.1 - 0.05)); // -5% to +5%
const quantity = Math.floor(10 + Math.random() * 90);
const pnl = (exitPrice - entryPrice) * quantity;
trades.push({
id: `trade_${i}`,
symbol,
entryPrice,
entryTime: new Date(now.getTime() - (i + 5) * 1000 * 60 * 60), // Hourly intervals
exitPrice,
exitTime: new Date(now.getTime() - i * 1000 * 60 * 60),
quantity,
pnl,
pnlPercent: ((exitPrice - entryPrice) / entryPrice) * 100
});
}
return trades;
}
}