Compare commits
10 Commits
422fecea7b
...
20b0881084
| Author | SHA1 | Date | |
|---|---|---|---|
| 20b0881084 | |||
| 84f3e91dc6 | |||
| 1071730978 | |||
|
|
3de8b5e00e | ||
| d281d7cd02 | |||
| 36cb672ce4 | |||
| 09e2c704ef | |||
| 38c7691615 | |||
| 2ea911b3c2 | |||
| 4fe3c9bb51 |
@@ -11,9 +11,6 @@ You are a senior .NET backend developer and experimental quant with deep experti
|
|||||||
## Quantitative Finance Core Principles
|
## Quantitative Finance Core Principles
|
||||||
- Prioritize numerical precision (use `decimal` for monetary calculations)
|
- Prioritize numerical precision (use `decimal` for monetary calculations)
|
||||||
- Implement proven financial mathematics (e.g., Black-Scholes, Monte Carlo methods)
|
- Implement proven financial mathematics (e.g., Black-Scholes, Monte Carlo methods)
|
||||||
- Optimize time-series processing for tick data/OHLCV series
|
|
||||||
- Validate models with historical backtesting frameworks
|
|
||||||
- Maintain audit trails for financial calculations
|
|
||||||
|
|
||||||
Key Principles
|
Key Principles
|
||||||
- Write concise, technical responses with accurate TypeScript examples.
|
- Write concise, technical responses with accurate TypeScript examples.
|
||||||
@@ -21,13 +18,11 @@ Key Principles
|
|||||||
- Prefer iteration and modularization over duplication.
|
- Prefer iteration and modularization over duplication.
|
||||||
- Use descriptive variable names with auxiliary verbs (e.g., isLoading).
|
- Use descriptive variable names with auxiliary verbs (e.g., isLoading).
|
||||||
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
|
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
|
||||||
- Favor named exports for components.
|
|
||||||
- Use the Receive an Object, Return an Object (RORO) pattern.
|
- Use the Receive an Object, Return an Object (RORO) pattern.
|
||||||
|
|
||||||
## Code Style and Structure
|
## Code Style and Structure
|
||||||
- Write concise, idiomatic C# code with accurate examples.
|
- Write concise, idiomatic C# code with accurate examples.
|
||||||
- Follow .NET and ASP.NET Core conventions and best practices.
|
- Follow .NET and ASP.NET Core conventions and best practices.
|
||||||
- Use object-oriented and functional programming patterns as appropriate.
|
|
||||||
- Prefer LINQ and lambda expressions for collection operations.
|
- Prefer LINQ and lambda expressions for collection operations.
|
||||||
- Use descriptive variable and method names (e.g., 'IsUserSignedIn', 'CalculateTotal').
|
- Use descriptive variable and method names (e.g., 'IsUserSignedIn', 'CalculateTotal').
|
||||||
- Structure files according to .NET conventions (Controllers, Models, Services, etc.).
|
- Structure files according to .NET conventions (Controllers, Models, Services, etc.).
|
||||||
@@ -41,7 +36,7 @@ Key Principles
|
|||||||
## C# and .NET Usage
|
## C# and .NET Usage
|
||||||
- Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment).
|
- Use C# 10+ features when appropriate (e.g., record types, pattern matching, null-coalescing assignment).
|
||||||
- Leverage built-in ASP.NET Core features and middleware.
|
- Leverage built-in ASP.NET Core features and middleware.
|
||||||
- Use MongoDb and Influxdb effectively for database operations.
|
- Use Postgres and Influxdb effectively for database operations.
|
||||||
|
|
||||||
## Syntax and Formatting
|
## Syntax and Formatting
|
||||||
- Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|
- Follow the C# Coding Conventions (https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|
||||||
@@ -57,8 +52,6 @@ Key Principles
|
|||||||
|
|
||||||
## API Design
|
## API Design
|
||||||
- Follow RESTful API design principles.
|
- Follow RESTful API design principles.
|
||||||
- Use attribute routing in controllers.
|
|
||||||
- Implement versioning for your API.
|
|
||||||
- Use action filters for cross-cutting concerns.
|
- Use action filters for cross-cutting concerns.
|
||||||
|
|
||||||
## Performance Optimization
|
## Performance Optimization
|
||||||
@@ -67,11 +60,6 @@ Key Principles
|
|||||||
- Use efficient LINQ queries and avoid N+1 query problems.
|
- Use efficient LINQ queries and avoid N+1 query problems.
|
||||||
- Implement pagination for large data sets.
|
- Implement pagination for large data sets.
|
||||||
|
|
||||||
## Testing
|
|
||||||
- Write unit tests using xUnit.
|
|
||||||
- Use Mock or NSubstitute for mocking dependencies.
|
|
||||||
- Implement integration tests for API endpoints.
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
- Give me advice when you see that some data should be carefully handled
|
- Give me advice when you see that some data should be carefully handled
|
||||||
|
|
||||||
@@ -81,7 +69,6 @@ Key Principles
|
|||||||
|
|
||||||
React/Tailwind/DaisyUI
|
React/Tailwind/DaisyUI
|
||||||
- Use functional components and TypeScript interfaces.
|
- Use functional components and TypeScript interfaces.
|
||||||
- Use declarative JSX.
|
|
||||||
- Use function, not const, for components.
|
- Use function, not const, for components.
|
||||||
- Use DaisyUI Tailwind Aria for components and styling.
|
- Use DaisyUI Tailwind Aria for components and styling.
|
||||||
- Implement responsive design with Tailwind CSS.
|
- Implement responsive design with Tailwind CSS.
|
||||||
@@ -106,5 +93,5 @@ Key Principles
|
|||||||
- Do not reference new react library if a component already exist in mollecules or atoms
|
- Do not reference new react library if a component already exist in mollecules or atoms
|
||||||
- After finishing the editing, build the project
|
- After finishing the editing, build the project
|
||||||
- you have to pass from controller -> application -> repository, do not inject repository inside controllers
|
- you have to pass from controller -> application -> repository, do not inject repository inside controllers
|
||||||
|
- dont use command line to edit file, use agent mode capabilities to do it
|
||||||
|
|
||||||
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components.
|
|
||||||
|
|||||||
204
CLAUDE.md
Normal file
204
CLAUDE.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# Managing Apps - Claude Code Guidelines
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
This is a quantitative finance application with .NET backend and React TypeScript frontend, focusing on algorithmic trading, market indicators, and financial mathematics.
|
||||||
|
|
||||||
|
## Core Architecture Principles
|
||||||
|
|
||||||
|
### Quantitative Finance Requirements
|
||||||
|
- **IMPORTANT**: Use `decimal` for all monetary calculations (never `double` or `float`)
|
||||||
|
- Implement proven financial mathematics (Black-Scholes, Monte Carlo, etc.)
|
||||||
|
- Optimize time-series processing for tick data/OHLCV series
|
||||||
|
- Validate models with historical backtesting frameworks
|
||||||
|
- Maintain audit trails for all financial calculations
|
||||||
|
- Prioritize numerical precision in all calculations
|
||||||
|
|
||||||
|
### Backend (.NET/C#) Guidelines
|
||||||
|
|
||||||
|
#### Code Style and Structure
|
||||||
|
- Write concise, idiomatic C# code following .NET conventions
|
||||||
|
- Use object-oriented and functional programming patterns appropriately
|
||||||
|
- Prefer LINQ and lambda expressions for collection operations
|
||||||
|
- Use descriptive variable and method names (e.g., `IsUserSignedIn`, `CalculateTotal`)
|
||||||
|
- Structure files according to .NET conventions (Controllers, Models, Services, etc.)
|
||||||
|
|
||||||
|
#### Naming Conventions
|
||||||
|
- **PascalCase**: Class names, method names, public members
|
||||||
|
- **camelCase**: Local variables, private fields
|
||||||
|
- **UPPERCASE**: Constants
|
||||||
|
- **Prefix interfaces with "I"**: `IUserService`, `IAccountRepository`
|
||||||
|
|
||||||
|
#### C# and .NET Usage
|
||||||
|
- Use C# 10+ features (record types, pattern matching, null-coalescing assignment)
|
||||||
|
- Leverage built-in ASP.NET Core features and middleware
|
||||||
|
- Use `var` for implicit typing when type is obvious
|
||||||
|
- Use MongoDb and Influxdb for database operations
|
||||||
|
|
||||||
|
#### Architecture Layers (YOU MUST FOLLOW)
|
||||||
|
1. **Controller** → **Application** → **Repository** (NEVER inject repository in controllers)
|
||||||
|
2. Always implement methods you create
|
||||||
|
3. Check existing code before creating new objects/methods
|
||||||
|
4. Update all layers when necessary (database to frontend)
|
||||||
|
|
||||||
|
#### Error Handling and Validation
|
||||||
|
- Use exceptions for exceptional cases, not control flow
|
||||||
|
- Implement proper error logging with .NET logging
|
||||||
|
- Use Data Annotations or Fluent Validation for model validation
|
||||||
|
- Return appropriate HTTP status codes and consistent error responses
|
||||||
|
- Services in `services/` directory must throw user-friendly errors for tanStackQuery
|
||||||
|
|
||||||
|
#### API Design
|
||||||
|
- Follow RESTful API design principles
|
||||||
|
- Use attribute routing in controllers
|
||||||
|
- Implement versioning for APIs
|
||||||
|
- Use Swagger/OpenAPI for documentation
|
||||||
|
|
||||||
|
#### Performance Optimization
|
||||||
|
- Use `async/await` for I/O-bound operations
|
||||||
|
- Implement caching strategies (IMemoryCache or distributed caching)
|
||||||
|
- Use efficient LINQ queries, avoid N+1 query problems
|
||||||
|
- Implement pagination for large datasets
|
||||||
|
|
||||||
|
### Frontend (React/TypeScript) Guidelines
|
||||||
|
|
||||||
|
#### Component Structure
|
||||||
|
- Use functional components with TypeScript interfaces
|
||||||
|
- Use declarative JSX
|
||||||
|
- Use `function`, not `const` for components
|
||||||
|
- Use DaisyUI Tailwind Aria for components and styling
|
||||||
|
- Implement responsive design with Tailwind CSS (mobile-first approach)
|
||||||
|
|
||||||
|
#### File Organization
|
||||||
|
- Use lowercase with dashes for directories: `components/auth-wizard`
|
||||||
|
- Place static content and interfaces at file end
|
||||||
|
- Use content variables for static content outside render functions
|
||||||
|
- Favor named exports for components
|
||||||
|
|
||||||
|
#### State Management
|
||||||
|
- Minimize `use client`, `useEffect`, and `setState`
|
||||||
|
- Favor React Server Components (RSC)
|
||||||
|
- Wrap client components in Suspense with fallback
|
||||||
|
- Use dynamic loading for non-critical components
|
||||||
|
- Use `useActionState` with react-hook-form for form validation
|
||||||
|
|
||||||
|
#### Error Handling
|
||||||
|
- Model expected errors as return values (avoid try/catch for expected errors)
|
||||||
|
- Use error boundaries for unexpected errors (`error.tsx`, `global-error.tsx`)
|
||||||
|
- Use `useActionState` to manage errors and return them to client
|
||||||
|
|
||||||
|
#### Component Library
|
||||||
|
- **DO NOT** reference new React libraries if components exist in `mollecules` or `atoms`
|
||||||
|
- Check existing components before creating new ones
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Build and Run Commands
|
||||||
|
```bash
|
||||||
|
# Backend
|
||||||
|
dotnet build
|
||||||
|
dotnet run --project src/Managing.Api
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
npm run build
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Regenerate API client (after backend changes)
|
||||||
|
cd src/Managing.Nswag && dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Client Generation
|
||||||
|
1. **NEVER** update `ManagingApi.ts` manually
|
||||||
|
2. After backend endpoint changes:
|
||||||
|
- Run the Managing.Api project
|
||||||
|
- Execute: `cd src/Managing.Nswag && dotnet build`
|
||||||
|
- This regenerates `ManagingApi.ts` automatically
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Write unit tests using xUnit for backend
|
||||||
|
- Use Mock or NSubstitute for mocking dependencies
|
||||||
|
- Implement integration tests for API endpoints
|
||||||
|
|
||||||
|
## Security Guidelines
|
||||||
|
- **IMPORTANT**: Handle sensitive data carefully (API keys, private keys, etc.)
|
||||||
|
- Implement proper authentication and authorization
|
||||||
|
- Use secure communication protocols
|
||||||
|
- Validate all user inputs
|
||||||
|
|
||||||
|
## Database Guidelines
|
||||||
|
- Use PostgreSQL for relational data
|
||||||
|
- Use InfluxDB for time-series data (candles, metrics)
|
||||||
|
- Use MongoDB for document storage
|
||||||
|
- Implement proper migrations
|
||||||
|
|
||||||
|
## Orleans Integration (Co-Hosting)
|
||||||
|
- Use `IGrainFactory` instead of `IClusterClient` for co-hosting scenarios
|
||||||
|
- Orleans automatically provides `IGrainFactory` when using `UseOrleans()`
|
||||||
|
- Avoid circular dependency issues by not manually registering `IClusterClient`
|
||||||
|
- Use Orleans grains for high-availability trading bots
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Backend Service Pattern
|
||||||
|
```csharp
|
||||||
|
public class ExampleService : IExampleService
|
||||||
|
{
|
||||||
|
private readonly IExampleRepository _repository;
|
||||||
|
private readonly ILogger<ExampleService> _logger;
|
||||||
|
|
||||||
|
public ExampleService(IExampleRepository repository, ILogger<ExampleService> logger)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Example> CreateExampleAsync(ExampleRequest request)
|
||||||
|
{
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Component Pattern
|
||||||
|
```typescript
|
||||||
|
interface ComponentProps {
|
||||||
|
isLoading: boolean;
|
||||||
|
data: SomeType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component({ isLoading, data }: ComponentProps): JSX.Element {
|
||||||
|
if (isLoading) return <Loader />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto">
|
||||||
|
{/* Component content */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Component;
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure Conventions
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── Managing.Api/ # API Controllers
|
||||||
|
├── Managing.Application/ # Business Logic
|
||||||
|
├── Managing.Domain/ # Domain Models
|
||||||
|
├── Managing.Infrastructure/ # Data Access
|
||||||
|
└── Managing.WebApp/ # React Frontend
|
||||||
|
└── src/
|
||||||
|
├── components/
|
||||||
|
│ ├── atoms/ # Basic components
|
||||||
|
│ ├── mollecules/ # Composite components
|
||||||
|
│ └── organism/ # Complex components
|
||||||
|
└── services/ # API calls
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Reminders
|
||||||
|
- Always implement methods you create
|
||||||
|
- Check existing code before creating new functionality
|
||||||
|
- Update multiple layers when necessary
|
||||||
|
- Build project after finishing edits
|
||||||
|
- Follow the controller → application → repository pattern
|
||||||
|
- Use existing components in mollecules/atoms before adding new libraries
|
||||||
|
- Use `IGrainFactory` for Orleans co-hosting (not `IClusterClient`)
|
||||||
255
Plan.md
Normal file
255
Plan.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Orleans Migration Plan for Managing Apps Trading Bot
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Migrate the `TradingBot` class to Microsoft Orleans grains for improved performance, scalability, and high availability while maintaining backward compatibility with the `Backtester` class.
|
||||||
|
|
||||||
|
## Current Architecture Analysis
|
||||||
|
|
||||||
|
### TradingBot Key Characteristics
|
||||||
|
- Long-running stateful service with complex state (positions, signals, candles, indicators)
|
||||||
|
- Timer-based execution via `InitWorker(Run)`
|
||||||
|
- Dependency injection via `IServiceScopeFactory`
|
||||||
|
- Persistence via `SaveBackup()` and `LoadBackup()`
|
||||||
|
- SignalR integration for real-time updates
|
||||||
|
|
||||||
|
### Backtester Requirements
|
||||||
|
- Creates TradingBot instances as regular classes (line 198: `_botFactory.CreateBacktestTradingBot`)
|
||||||
|
- Runs synchronous backtesting without Orleans overhead
|
||||||
|
- Needs direct object manipulation for performance
|
||||||
|
|
||||||
|
## 1. Orleans Grain Design
|
||||||
|
|
||||||
|
### A. Create ITradingBotGrain Interface
|
||||||
|
```csharp
|
||||||
|
// src/Managing.Application.Abstractions/Grains/ITradingBotGrain.cs
|
||||||
|
public interface ITradingBotGrain : IGrainWithStringKey
|
||||||
|
{
|
||||||
|
Task StartAsync();
|
||||||
|
Task StopAsync();
|
||||||
|
Task<BotStatus> GetStatusAsync();
|
||||||
|
Task<TradingBotConfig> GetConfigurationAsync();
|
||||||
|
Task<bool> UpdateConfigurationAsync(TradingBotConfig newConfig);
|
||||||
|
Task<Position> OpenPositionManuallyAsync(TradeDirection direction);
|
||||||
|
Task ToggleIsForWatchOnlyAsync();
|
||||||
|
Task<TradingBotResponse> GetBotDataAsync();
|
||||||
|
Task LoadBackupAsync(BotBackup backup);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. Modify TradingBot Class
|
||||||
|
```csharp
|
||||||
|
// src/Managing.Application/Bots/TradingBot.cs
|
||||||
|
public class TradingBot : Grain, ITradingBotGrain, ITradingBot
|
||||||
|
{
|
||||||
|
// Keep existing implementation but add Orleans-specific methods
|
||||||
|
// Add grain lifecycle management
|
||||||
|
// Replace IServiceScopeFactory with Orleans DI
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Program.cs Orleans Configuration
|
||||||
|
|
||||||
|
Add to `src/Managing.Api/Program.cs` after line 188:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Orleans Configuration
|
||||||
|
builder.Host.UseOrleans(siloBuilder =>
|
||||||
|
{
|
||||||
|
siloBuilder
|
||||||
|
.UseLocalhostClustering() // For local development
|
||||||
|
.ConfigureLogging(logging => logging.AddConsole())
|
||||||
|
.UseDashboard(options => { options.Port = 8080; })
|
||||||
|
.AddMemoryGrainStorageAsDefault()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
// Register existing services for Orleans DI
|
||||||
|
services.AddSingleton<IExchangeService, ExchangeService>();
|
||||||
|
services.AddSingleton<IAccountService, AccountService>();
|
||||||
|
services.AddSingleton<ITradingService, TradingService>();
|
||||||
|
services.AddSingleton<IMessengerService, MessengerService>();
|
||||||
|
services.AddSingleton<IBackupBotService, BackupBotService>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Production clustering configuration
|
||||||
|
if (builder.Environment.IsProduction())
|
||||||
|
{
|
||||||
|
siloBuilder
|
||||||
|
.UseAdoNetClustering(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
})
|
||||||
|
.UseAdoNetReminderService(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Orleans Client Configuration (for accessing grains from controllers)
|
||||||
|
builder.Services.AddOrleansClient(clientBuilder =>
|
||||||
|
{
|
||||||
|
clientBuilder.UseLocalhostClustering();
|
||||||
|
|
||||||
|
if (builder.Environment.IsProduction())
|
||||||
|
{
|
||||||
|
clientBuilder.UseAdoNetClustering(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Conditional Bot Instantiation Strategy
|
||||||
|
|
||||||
|
### A. Enhanced BotFactory Pattern
|
||||||
|
```csharp
|
||||||
|
// src/Managing.Application/Bots/Base/BotFactory.cs
|
||||||
|
public class BotFactory : IBotFactory
|
||||||
|
{
|
||||||
|
private readonly IClusterClient _clusterClient;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public async Task<ITradingBot> CreateTradingBotAsync(TradingBotConfig config, bool useGrain = true)
|
||||||
|
{
|
||||||
|
if (config.IsForBacktest || !useGrain)
|
||||||
|
{
|
||||||
|
// For backtesting: Create regular class instance
|
||||||
|
return new TradingBot(
|
||||||
|
_serviceProvider.GetService<ILogger<TradingBot>>(),
|
||||||
|
_serviceProvider.GetService<IServiceScopeFactory>(),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For live trading: Use Orleans grain
|
||||||
|
var grain = _clusterClient.GetGrain<ITradingBotGrain>(config.Name);
|
||||||
|
return new TradingBotGrainProxy(grain, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. TradingBotGrainProxy (Adapter Pattern)
|
||||||
|
```csharp
|
||||||
|
// src/Managing.Application/Bots/TradingBotGrainProxy.cs
|
||||||
|
public class TradingBotGrainProxy : ITradingBot
|
||||||
|
{
|
||||||
|
private readonly ITradingBotGrain _grain;
|
||||||
|
private TradingBotConfig _config;
|
||||||
|
|
||||||
|
public TradingBotGrainProxy(ITradingBotGrain grain, TradingBotConfig config)
|
||||||
|
{
|
||||||
|
_grain = grain;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start() => await _grain.StartAsync();
|
||||||
|
public async Task Stop() => await _grain.StopAsync();
|
||||||
|
|
||||||
|
// Implement all ITradingBot methods by delegating to grain
|
||||||
|
// This maintains compatibility with existing bot management code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### C. Backtester Compatibility
|
||||||
|
In `Backtester.cs` (line 198), the factory call remains unchanged:
|
||||||
|
```csharp
|
||||||
|
// This will automatically create a regular class instance due to IsForBacktest = true
|
||||||
|
var tradingBot = await _botFactory.CreateBacktestTradingBot(config);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Orleans Grain State Management
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// src/Managing.Application/Bots/TradingBotGrainState.cs
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class TradingBotGrainState
|
||||||
|
{
|
||||||
|
[Id(0)] public TradingBotConfig Config { get; set; }
|
||||||
|
[Id(1)] public HashSet<LightSignal> Signals { get; set; }
|
||||||
|
[Id(2)] public List<Position> Positions { get; set; }
|
||||||
|
[Id(3)] public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||||
|
[Id(4)] public BotStatus Status { get; set; }
|
||||||
|
[Id(5)] public DateTime StartupTime { get; set; }
|
||||||
|
[Id(6)] public DateTime CreateDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated TradingBot grain
|
||||||
|
public class TradingBot : Grain<TradingBotGrainState>, ITradingBotGrain
|
||||||
|
{
|
||||||
|
private IDisposable _timer;
|
||||||
|
|
||||||
|
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Initialize grain state and start timer-based execution
|
||||||
|
if (State.Config != null && State.Status == BotStatus.Running)
|
||||||
|
{
|
||||||
|
await StartTimerAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartTimerAsync()
|
||||||
|
{
|
||||||
|
var interval = CandleExtensions.GetIntervalFromTimeframe(State.Config.Timeframe);
|
||||||
|
_timer = RegisterTimer(async _ => await Run(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(interval));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Implementation Roadmap
|
||||||
|
|
||||||
|
### Phase 1: Infrastructure Setup
|
||||||
|
1. **Add Orleans packages** (already done in Managing.Api.csproj)
|
||||||
|
2. **Configure Orleans in Program.cs** with clustering and persistence
|
||||||
|
3. **Create grain interfaces and state classes**
|
||||||
|
|
||||||
|
### Phase 2: Core Migration
|
||||||
|
1. **Create ITradingBotGrain interface** with async methods
|
||||||
|
2. **Modify TradingBot class** to inherit from `Grain<TradingBotGrainState>`
|
||||||
|
3. **Implement TradingBotGrainProxy** for compatibility
|
||||||
|
4. **Update BotFactory** with conditional instantiation logic
|
||||||
|
|
||||||
|
### Phase 3: Service Integration
|
||||||
|
1. **Replace IServiceScopeFactory** with Orleans dependency injection
|
||||||
|
2. **Update timer management** to use Orleans grain timers
|
||||||
|
3. **Migrate state persistence** from SaveBackup/LoadBackup to Orleans state
|
||||||
|
4. **Update bot management services** to work with grains
|
||||||
|
|
||||||
|
### Phase 4: Testing & Optimization
|
||||||
|
1. **Test backtesting compatibility** (should remain unchanged)
|
||||||
|
2. **Performance testing** with multiple concurrent bots
|
||||||
|
3. **High availability testing** with node failures
|
||||||
|
4. **Memory and resource optimization**
|
||||||
|
|
||||||
|
## Key Benefits
|
||||||
|
|
||||||
|
1. **High Availability**: Orleans automatic failover and grain migration
|
||||||
|
2. **Scalability**: Distributed bot execution across multiple nodes
|
||||||
|
3. **Performance**: Reduced serialization overhead, efficient state management
|
||||||
|
4. **Backward Compatibility**: Backtester continues using regular classes
|
||||||
|
5. **Simplified State Management**: Orleans handles persistence automatically
|
||||||
|
|
||||||
|
## Migration Considerations
|
||||||
|
|
||||||
|
1. **Async Conversion**: All bot operations become async
|
||||||
|
2. **State Serialization**: Ensure all state classes are serializable
|
||||||
|
3. **Timer Management**: Replace custom timers with Orleans grain timers
|
||||||
|
4. **Dependency Injection**: Adapt from ASP.NET Core DI to Orleans DI
|
||||||
|
5. **SignalR Integration**: Update to work with distributed grains
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
- ✅ Orleans package added to Managing.Api.csproj
|
||||||
|
- ✅ Orleans configuration implemented in Program.cs
|
||||||
|
- ✅ ITradingBotGrain interface created
|
||||||
|
- ✅ TradingBotGrainState class created
|
||||||
|
- ✅ TradingBotGrain implementation completed
|
||||||
|
- ✅ TradingBotResponse model created
|
||||||
|
- ✅ TradingBotProxy adapter pattern implemented
|
||||||
|
- ✅ Original TradingBot class preserved for backtesting
|
||||||
|
- ✅ BotService conditional logic implemented for all creation methods
|
||||||
|
- ⏳ Testing Orleans integration
|
||||||
189
orleans-plan.md
Normal file
189
orleans-plan.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
Todo List
|
||||||
|
Phase 1: Keep TradingBotBase Unchanged (Composition Approach) ✅ COMPLETE
|
||||||
|
[✅] File: src/Managing.Application/Bots/TradingBotBase.cs
|
||||||
|
[✅] Keep class as concrete (not abstract)
|
||||||
|
[✅] No Orleans-specific methods needed
|
||||||
|
[✅] Preserve all existing functionality
|
||||||
|
[✅] Ensure it remains reusable for direct instantiation and Orleans composition
|
||||||
|
|
||||||
|
Phase 2: Create Orleans Wrapper Grains (Composition)
|
||||||
|
[✅] File: src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
|
||||||
|
[✅] Inherit from Grain<TradingBotGrainState> and implement ITradingBotGrain
|
||||||
|
[✅] Use composition: private TradingBotBase _tradingBot
|
||||||
|
[✅] Implement Orleans lifecycle methods (OnActivateAsync, OnDeactivateAsync)
|
||||||
|
[✅] Delegate trading operations to _tradingBot instance
|
||||||
|
[✅] Handle Orleans timer management for bot execution
|
||||||
|
[✅] Implement state persistence between grain state and TradingBotBase
|
||||||
|
[✅] Add configuration validation for live trading
|
||||||
|
[✅] Implement all ITradingBotGrain methods as wrappers
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application/Bots/Grains/BacktestTradingBotGrain.cs
|
||||||
|
[✅] Inherit from Grain<TradingBotGrainState> and implement IBacktestTradingBotGrain
|
||||||
|
[✅] Use composition: private TradingBotBase _tradingBot
|
||||||
|
[✅] Implement Orleans lifecycle methods for backtest execution
|
||||||
|
[✅] Delegate trading operations to _tradingBot instance
|
||||||
|
[✅] Handle backtest-specific candle processing (no timer)
|
||||||
|
[✅] Implement state persistence for backtest scenarios
|
||||||
|
[✅] Add configuration validation for backtesting
|
||||||
|
[✅] Implement all ITradingBotGrain methods as wrappers
|
||||||
|
[✅] Add backtest-specific methods: RunBacktestAsync, GetBacktestProgressAsync (following GetBacktestingResult pattern)
|
||||||
|
[✅] Stateless design - no state persistence, fresh TradingBotBase instance per backtest
|
||||||
|
[✅] Simplified interface - Start/Stop are no-ops, other methods throw exceptions for backtest mode
|
||||||
|
[✅] StatelessWorker attribute - grain doesn't inherit from Grain<T> but implements interface
|
||||||
|
[✅] Config passed as parameter - no state dependency, config passed to RunBacktestAsync method
|
||||||
|
[✅] **NEW: Orleans Serialization Support**
|
||||||
|
[✅] Return LightBacktest instead of Backtest for safe Orleans serialization
|
||||||
|
[✅] Add ConvertToLightBacktest method to map Backtest to LightBacktest
|
||||||
|
[✅] Handle type conversions (decimal? to double? for SharpeRatio, etc.)
|
||||||
|
[✅] Ensure all properties are Orleans-serializable
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Domain/Backtests/LightBacktest.cs
|
||||||
|
[✅] **NEW: Add Orleans Serialization Attributes**
|
||||||
|
[✅] Add [GenerateSerializer] attribute for Orleans serialization
|
||||||
|
[✅] Add [Id(n)] attributes to all properties for proper serialization
|
||||||
|
[✅] Add using Orleans; statement
|
||||||
|
[✅] Ensure all property types are Orleans-serializable
|
||||||
|
[✅] Match property types with LightBacktestResponse for consistency
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application.Abstractions/Grains/IBacktestTradingBotGrain.cs
|
||||||
|
[✅] **NEW: Update Interface for LightBacktest**
|
||||||
|
[✅] Change RunBacktestAsync return type from Backtest to LightBacktest
|
||||||
|
[✅] Update method documentation to reflect LightBacktest usage
|
||||||
|
[✅] Ensure interface is Orleans-compatible
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application/Backtesting/Backtester.cs
|
||||||
|
[✅] Inject IGrainFactory dependency
|
||||||
|
[✅] Update RunBacktestWithCandles to use Orleans grain instead of direct bot creation
|
||||||
|
[✅] Remove GetBacktestingResult method (logic moved to grain)
|
||||||
|
[✅] Remove helper methods (AggregateValues, GetIndicatorsValues) - moved to grain
|
||||||
|
[✅] Simplified backtesting flow - Backtester orchestrates, grain executes
|
||||||
|
[✅] Fixed Orleans serialization issue - CreateCleanConfigForOrleans method removes FixedSizeQueue objects
|
||||||
|
[✅] Created LightIndicator and LightScenario classes for Orleans serialization
|
||||||
|
[✅] Updated TradingBotConfig to use LightScenario instead of Scenario
|
||||||
|
[✅] Simplified serialization - no more FixedSizeQueue or User properties in Orleans data
|
||||||
|
[✅] Updated all application code to use LightScenario conversions
|
||||||
|
[✅] Main application builds successfully with Orleans integration
|
||||||
|
[✅] **NEW: Update for LightBacktest Integration**
|
||||||
|
[✅] Update interface to return LightBacktest instead of Backtest
|
||||||
|
[✅] Update RunTradingBotBacktest methods to return LightBacktest
|
||||||
|
[✅] Remove conversion methods (no longer needed)
|
||||||
|
[✅] Simplify Orleans grain calls to return LightBacktest directly
|
||||||
|
[✅] Update all dependent services to work with LightBacktest
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application.Abstractions/Services/IBacktester.cs
|
||||||
|
[✅] **NEW: Update Interface for LightBacktest**
|
||||||
|
[✅] Change main backtest methods to return LightBacktest
|
||||||
|
[✅] Keep full Backtest methods for database retrieval
|
||||||
|
[✅] Update method documentation for LightBacktest usage
|
||||||
|
[✅] Ensure backward compatibility where needed
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Api/Controllers/BacktestController.cs
|
||||||
|
[✅] **NEW: Update Controller for LightBacktest**
|
||||||
|
[✅] Update Run method to return LightBacktest instead of Backtest
|
||||||
|
[✅] Update method documentation to explain LightBacktest usage
|
||||||
|
[✅] Remove unused notification method (handled in Orleans grain)
|
||||||
|
[✅] Update variable declarations and return statements
|
||||||
|
[✅] Ensure API responses are consistent with LightBacktest structure
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application.Workers/StatisticService.cs
|
||||||
|
[✅] **NEW: Update for LightBacktest Compatibility**
|
||||||
|
[✅] Update GetSignals method to handle LightBacktest (no signals data)
|
||||||
|
[✅] Add warning log when signals data is not available
|
||||||
|
[✅] Return empty list for signals (full data available via database lookup)
|
||||||
|
|
||||||
|
[✅] File: src/Managing.Application/GeneticService.cs
|
||||||
|
[✅] **NEW: Update for LightBacktest Compatibility**
|
||||||
|
[✅] Update TradingBotFitness.Evaluate to work with LightBacktest
|
||||||
|
[✅] Update CalculateFitness method to accept LightBacktest
|
||||||
|
[✅] Ensure genetic algorithm works with lightweight backtest data
|
||||||
|
|
||||||
|
[ ] File: src/Managing.Application/Bots/Grains/TradingBotGrainProxy.cs
|
||||||
|
[ ] Fix remaining test compilation errors (6 scenario conversion errors in BotsTests.cs)
|
||||||
|
[ ] Create proxy class that implements ITradingBot interface
|
||||||
|
[ ] Wrap Orleans grain calls for seamless integration
|
||||||
|
[ ] Maintain compatibility with existing ITradingBot consumers
|
||||||
|
[ ] Handle async/await conversion between Orleans and synchronous calls
|
||||||
|
|
||||||
|
Phase 3: Update BotService for Conditional Instantiation
|
||||||
|
[ ] File: src/Managing.Application/ManageBot/BotService.cs
|
||||||
|
[ ] Remove _botTasks dictionary (replaced by Orleans grain management)
|
||||||
|
[ ] Remove BotTaskWrapper class (no longer needed)
|
||||||
|
[ ] Inject IGrainFactory for Orleans grain creation
|
||||||
|
[ ] Update CreateTradingBot() with conditional logic:
|
||||||
|
[ ] If IsForBacktest: return new TradingBotBase() (direct instantiation)
|
||||||
|
[ ] If live trading: return new TradingBotGrainProxy(grain) (Orleans wrapper)
|
||||||
|
[ ] Update CreateBacktestTradingBot() with same conditional logic
|
||||||
|
[ ] Update all bot management methods to work with both direct and grain instances
|
||||||
|
[ ] Use Guid for grain identification
|
||||||
|
|
||||||
|
Phase 4: Update Orleans Interface and State
|
||||||
|
[ ] File: src/Managing.Application.Abstractions/Grains/ITradingBotGrain.cs
|
||||||
|
[ ] Update to use IGrainWithGuidKey
|
||||||
|
[ ] Add InitializeAsync(TradingBotConfig config) method
|
||||||
|
[ ] Add RestartAsync() method
|
||||||
|
[ ] Add DeleteAsync() method
|
||||||
|
[ ] Add GetBotDataAsync() method
|
||||||
|
[ ] Ensure all methods are async and Orleans-compatible
|
||||||
|
|
||||||
|
[ ] File: src/Managing.Application/Bots/TradingBotGrainState.cs
|
||||||
|
[ ] Ensure all properties are Orleans-serializable
|
||||||
|
[ ] Add methods for state synchronization with TradingBotBase
|
||||||
|
[ ] Implement backup/restore functionality
|
||||||
|
[ ] Add validation for state consistency
|
||||||
|
|
||||||
|
Phase 5: Update Dependencies and Configuration
|
||||||
|
[ ] File: src/Managing.Bootstrap/ApiBootstrap.cs
|
||||||
|
[ ] Register Orleans grains (LiveTradingBotGrain, BacktestTradingBotGrain)
|
||||||
|
[ ] Keep existing bot service registrations for backward compatibility
|
||||||
|
[ ] Add grain factory registration
|
||||||
|
[ ] Configure Orleans clustering and persistence
|
||||||
|
|
||||||
|
Phase 6: Testing and Validation
|
||||||
|
[ ] Test direct TradingBotBase instantiation (backtesting)
|
||||||
|
[ ] Test LiveTradingBotGrain functionality (live trading)
|
||||||
|
[ ] Test BacktestTradingBotGrain functionality (Orleans backtesting)
|
||||||
|
[ ] Test BotService conditional instantiation
|
||||||
|
[ ] Test Orleans reminder functionality
|
||||||
|
[ ] Test grain lifecycle management
|
||||||
|
[ ] Test state persistence and recovery
|
||||||
|
[ ] Test TradingBotGrainProxy compatibility
|
||||||
|
[✅] **NEW: Test LightBacktest Serialization**
|
||||||
|
[✅] Verify Orleans serialization works correctly
|
||||||
|
[✅] Test LightBacktest to Backtest conversion (if needed)
|
||||||
|
[✅] Verify API responses with LightBacktest data
|
||||||
|
[✅] Test genetic algorithm with LightBacktest
|
||||||
|
|
||||||
|
Benefits of Composition Approach
|
||||||
|
✅ TradingBotBase remains concrete and reusable
|
||||||
|
✅ No Orleans-specific code in core trading logic
|
||||||
|
✅ Backward compatibility maintained
|
||||||
|
✅ Clean separation of concerns
|
||||||
|
✅ Easier testing and maintenance
|
||||||
|
✅ Follows SOLID principles
|
||||||
|
✅ Flexible architecture for future changes
|
||||||
|
✅ **NEW: Orleans Serialization Benefits**
|
||||||
|
✅ LightBacktest provides efficient serialization
|
||||||
|
✅ Reduced memory footprint for Orleans communication
|
||||||
|
✅ Safe type serialization with GenerateSerializer attributes
|
||||||
|
✅ Consistent data structure across Orleans grains and API responses
|
||||||
|
|
||||||
|
Implementation Order
|
||||||
|
Phase 1: Keep TradingBotBase unchanged (preserve existing functionality) ✅ COMPLETE
|
||||||
|
Phase 2: Create Orleans wrapper grains (composition approach) ✅ COMPLETE
|
||||||
|
Phase 3: Update BotService for conditional instantiation
|
||||||
|
Phase 4: Update Orleans interface and state management
|
||||||
|
Phase 5: Update dependencies and configuration
|
||||||
|
Phase 6: Testing and validation
|
||||||
|
|
||||||
|
Current Status
|
||||||
|
✅ Orleans infrastructure setup
|
||||||
|
✅ TradingBotBase contains all core logic (keep as-is)
|
||||||
|
✅ LiveTradingBot.cs exists (will be replaced by grain)
|
||||||
|
✅ Phase 1 Complete - TradingBotBase ready for composition approach
|
||||||
|
✅ Phase 2 Complete - Orleans wrapper grains created and working
|
||||||
|
✅ **NEW: LightBacktest Orleans Serialization Complete**
|
||||||
|
✅ BacktestTradingBotGrain returns LightBacktest for safe serialization
|
||||||
|
✅ All interfaces and services updated to use LightBacktest
|
||||||
|
✅ API controllers updated for LightBacktest responses
|
||||||
|
✅ Application builds successfully with Orleans integration
|
||||||
|
✅ Ready to start Phase 3 (update BotService for conditional instantiation)
|
||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "managing-apps",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"genetic-js": "^0.1.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/genetic-js": {
|
|
||||||
"version": "0.1.14",
|
|
||||||
"resolved": "https://registry.npmjs.org/genetic-js/-/genetic-js-0.1.14.tgz",
|
|
||||||
"integrity": "sha512-HHm21naCEF1EVKTWPFzKX4ENB7Nn/my4kTy2POi4u/2gB0XPUOh8oDlhhESVCZVBge3b7nuLrZNZNAt4ObH19Q==",
|
|
||||||
"license": "BSD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"genetic-js": "^0.1.14"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"PostgreSql": {
|
||||||
|
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
|
||||||
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://srv-captain--influx-db:8086/",
|
"Url": "http://srv-captain--influx-db:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ public class BacktestController : BaseController
|
|||||||
{
|
{
|
||||||
private readonly IHubContext<BacktestHub> _hubContext;
|
private readonly IHubContext<BacktestHub> _hubContext;
|
||||||
private readonly IBacktester _backtester;
|
private readonly IBacktester _backtester;
|
||||||
private readonly IScenarioService _scenarioService;
|
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IGeneticService _geneticService;
|
private readonly IGeneticService _geneticService;
|
||||||
@@ -47,7 +46,6 @@ public class BacktestController : BaseController
|
|||||||
public BacktestController(
|
public BacktestController(
|
||||||
IHubContext<BacktestHub> hubContext,
|
IHubContext<BacktestHub> hubContext,
|
||||||
IBacktester backtester,
|
IBacktester backtester,
|
||||||
IScenarioService scenarioService,
|
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMoneyManagementService moneyManagementService,
|
IMoneyManagementService moneyManagementService,
|
||||||
IGeneticService geneticService,
|
IGeneticService geneticService,
|
||||||
@@ -55,7 +53,6 @@ public class BacktestController : BaseController
|
|||||||
{
|
{
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
_backtester = backtester;
|
_backtester = backtester;
|
||||||
_scenarioService = scenarioService;
|
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
_geneticService = geneticService;
|
_geneticService = geneticService;
|
||||||
@@ -245,7 +242,8 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Sort order must be 'asc' or 'desc'");
|
return BadRequest("Sort order must be 'asc' or 'desc'");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (backtests, totalCount) = await _backtester.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
|
var (backtests, totalCount) =
|
||||||
|
await _backtester.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||||
|
|
||||||
var response = new PaginatedBacktestsResponse
|
var response = new PaginatedBacktestsResponse
|
||||||
@@ -279,14 +277,14 @@ public class BacktestController : BaseController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a backtest with the specified configuration.
|
/// Runs a backtest with the specified configuration.
|
||||||
/// The returned backtest includes a complete TradingBotConfig that preserves all
|
/// Returns a lightweight backtest result for efficient processing.
|
||||||
/// settings including nullable MaxPositionTimeHours for easy bot deployment.
|
/// Use the returned ID to retrieve the full backtest data from the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The backtest request containing configuration and parameters.</param>
|
/// <param name="request">The backtest request containing configuration and parameters.</param>
|
||||||
/// <returns>The result of the backtest with complete configuration.</returns>
|
/// <returns>The lightweight result of the backtest with essential data.</returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("Run")]
|
[Route("Run")]
|
||||||
public async Task<ActionResult<Backtest>> Run([FromBody] RunBacktestRequest request)
|
public async Task<ActionResult<LightBacktest>> Run([FromBody] RunBacktestRequest request)
|
||||||
{
|
{
|
||||||
if (request?.Config == null)
|
if (request?.Config == null)
|
||||||
{
|
{
|
||||||
@@ -310,7 +308,7 @@ public class BacktestController : BaseController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Backtest backtestResult = null;
|
LightBacktest backtestResult = null;
|
||||||
var account = await _accountService.GetAccount(request.Config.AccountName, true, false);
|
var account = await _accountService.GetAccount(request.Config.AccountName, true, false);
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
@@ -367,7 +365,9 @@ public class BacktestController : BaseController
|
|||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = request.Config.Ticker,
|
Ticker = request.Config.Ticker,
|
||||||
ScenarioName = request.Config.ScenarioName,
|
ScenarioName = request.Config.ScenarioName,
|
||||||
Scenario = scenario, // Use the converted scenario object
|
Scenario = scenario != null
|
||||||
|
? LightScenario.FromScenario(scenario)
|
||||||
|
: null, // Convert to LightScenario for Orleans
|
||||||
Timeframe = request.Config.Timeframe,
|
Timeframe = request.Config.Timeframe,
|
||||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||||
BotTradingBalance = request.Config.BotTradingBalance,
|
BotTradingBalance = request.Config.BotTradingBalance,
|
||||||
@@ -395,7 +395,8 @@ public class BacktestController : BaseController
|
|||||||
request.WithCandles,
|
request.WithCandles,
|
||||||
null); // No requestId for regular backtests
|
null); // No requestId for regular backtests
|
||||||
|
|
||||||
await NotifyBacktesingSubscriberAsync(backtestResult);
|
// Note: Notification is handled within the Orleans grain for LightBacktest
|
||||||
|
// The full Backtest data can be retrieved from the database using the ID if needed
|
||||||
|
|
||||||
return Ok(backtestResult);
|
return Ok(backtestResult);
|
||||||
}
|
}
|
||||||
@@ -705,17 +706,6 @@ public class BacktestController : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies subscribers about the backtesting results via SignalR.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="backtesting">The backtest result to notify subscribers about.</param>
|
|
||||||
private async Task NotifyBacktesingSubscriberAsync(Backtest backtesting)
|
|
||||||
{
|
|
||||||
if (backtesting != null)
|
|
||||||
{
|
|
||||||
await _hubContext.Clients.All.SendAsync("BacktestsSubscription", backtesting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
|
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ public class BotController : BaseController
|
|||||||
AccountName = request.Config.AccountName,
|
AccountName = request.Config.AccountName,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = request.Config.Ticker,
|
Ticker = request.Config.Ticker,
|
||||||
Scenario = scenario, // Use the converted scenario object
|
Scenario = LightScenario.FromScenario(scenario), // Convert to LightScenario for Orleans
|
||||||
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
||||||
Timeframe = request.Config.Timeframe,
|
Timeframe = request.Config.Timeframe,
|
||||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||||
@@ -782,7 +782,7 @@ public class BotController : BaseController
|
|||||||
AccountName = request.Config.AccountName,
|
AccountName = request.Config.AccountName,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = request.Config.Ticker,
|
Ticker = request.Config.Ticker,
|
||||||
Scenario = scenarioForUpdate, // Use the converted scenario object
|
Scenario = LightScenario.FromScenario(scenarioForUpdate), // Convert to LightScenario for Orleans
|
||||||
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
||||||
Timeframe = request.Config.Timeframe,
|
Timeframe = request.Config.Timeframe,
|
||||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||||
|
|||||||
@@ -493,10 +493,10 @@ public class DataController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a summary of platform activity across all agents
|
/// Retrieves a summary of platform activity across all agents (platform-level data only)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
||||||
/// <returns>A summary of platform activity including per-agent statistics</returns>
|
/// <returns>A summary of platform activity without individual agent details</returns>
|
||||||
[HttpGet("GetPlatformSummary")]
|
[HttpGet("GetPlatformSummary")]
|
||||||
public async Task<ActionResult<PlatformSummaryViewModel>> GetPlatformSummary(string timeFilter = "Total")
|
public async Task<ActionResult<PlatformSummaryViewModel>> GetPlatformSummary(string timeFilter = "Total")
|
||||||
{
|
{
|
||||||
@@ -533,6 +533,75 @@ public class DataController : ControllerBase
|
|||||||
decimal totalPlatformVolume = 0;
|
decimal totalPlatformVolume = 0;
|
||||||
decimal totalPlatformVolumeLast24h = 0;
|
decimal totalPlatformVolumeLast24h = 0;
|
||||||
|
|
||||||
|
// Calculate totals from all agents
|
||||||
|
foreach (var agent in agentsWithStrategies)
|
||||||
|
{
|
||||||
|
var strategies = agent.Value;
|
||||||
|
|
||||||
|
if (strategies.Count == 0)
|
||||||
|
{
|
||||||
|
continue; // Skip agents with no strategies
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine all positions from all strategies
|
||||||
|
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
|
||||||
|
|
||||||
|
// Calculate agent metrics for platform totals
|
||||||
|
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
|
||||||
|
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
|
||||||
|
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
|
||||||
|
|
||||||
|
// Add to platform totals
|
||||||
|
totalPlatformPnL += totalPnL;
|
||||||
|
totalPlatformVolume += totalVolume;
|
||||||
|
totalPlatformVolumeLast24h += volumeLast24h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the platform totals
|
||||||
|
summary.TotalPlatformPnL = totalPlatformPnL;
|
||||||
|
summary.TotalPlatformVolume = totalPlatformVolume;
|
||||||
|
summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h;
|
||||||
|
|
||||||
|
// Cache the results for 5 minutes
|
||||||
|
_cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
return Ok(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a list of agent summaries for the agent index page
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
||||||
|
/// <returns>A list of agent summaries sorted by performance</returns>
|
||||||
|
[HttpGet("GetAgentIndex")]
|
||||||
|
public async Task<ActionResult<AgentIndexViewModel>> GetAgentIndex(string timeFilter = "Total")
|
||||||
|
{
|
||||||
|
// Validate time filter
|
||||||
|
var validTimeFilters = new[] { "24H", "3D", "1W", "1M", "1Y", "Total" };
|
||||||
|
if (!validTimeFilters.Contains(timeFilter))
|
||||||
|
{
|
||||||
|
timeFilter = "Total"; // Default to Total if invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
string cacheKey = $"AgentIndex_{timeFilter}";
|
||||||
|
|
||||||
|
// Check if the agent index is already cached
|
||||||
|
var cachedIndex = _cacheService.GetValue<AgentIndexViewModel>(cacheKey);
|
||||||
|
|
||||||
|
if (cachedIndex != null)
|
||||||
|
{
|
||||||
|
return Ok(cachedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all agents and their strategies
|
||||||
|
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
|
||||||
|
|
||||||
|
// Create the agent index response
|
||||||
|
var agentIndex = new AgentIndexViewModel
|
||||||
|
{
|
||||||
|
TimeFilter = timeFilter
|
||||||
|
};
|
||||||
|
|
||||||
// Create summaries for each agent
|
// Create summaries for each agent
|
||||||
foreach (var agent in agentsWithStrategies)
|
foreach (var agent in agentsWithStrategies)
|
||||||
{
|
{
|
||||||
@@ -583,26 +652,16 @@ public class DataController : ControllerBase
|
|||||||
VolumeLast24h = volumeLast24h
|
VolumeLast24h = volumeLast24h
|
||||||
};
|
};
|
||||||
|
|
||||||
summary.AgentSummaries.Add(agentSummary);
|
agentIndex.AgentSummaries.Add(agentSummary);
|
||||||
|
|
||||||
// Add to platform totals
|
|
||||||
totalPlatformPnL += totalPnL;
|
|
||||||
totalPlatformVolume += totalVolume;
|
|
||||||
totalPlatformVolumeLast24h += volumeLast24h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the platform totals
|
|
||||||
summary.TotalPlatformPnL = totalPlatformPnL;
|
|
||||||
summary.TotalPlatformVolume = totalPlatformVolume;
|
|
||||||
summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h;
|
|
||||||
|
|
||||||
// Sort agent summaries by total PnL (highest first)
|
// Sort agent summaries by total PnL (highest first)
|
||||||
summary.AgentSummaries = summary.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList();
|
agentIndex.AgentSummaries = agentIndex.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList();
|
||||||
|
|
||||||
// Cache the results for 5 minutes
|
// Cache the results for 5 minutes
|
||||||
_cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5));
|
_cacheService.SaveValue(cacheKey, agentIndex, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
return Ok(summary);
|
return Ok(agentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="9.0.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="9.0.0"/>
|
||||||
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
|
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5"/>
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5"/>
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Core" Version="9.2.1"/>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1"/>
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1"/>
|
||||||
<PackageReference Include="NSwag.AspNetCore" Version="14.0.7"/>
|
<PackageReference Include="NSwag.AspNetCore" Version="14.0.7"/>
|
||||||
|
<PackageReference Include="OrleansDashboard" Version="8.2.0"/>
|
||||||
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1"/>
|
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1"/>
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
|
||||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0"/>
|
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0"/>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Managing.Api.Models.Responses
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Platform-wide statistics including per-agent summaries
|
/// Platform-wide statistics without individual agent details
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlatformSummaryViewModel
|
public class PlatformSummaryViewModel
|
||||||
{
|
{
|
||||||
@@ -92,7 +92,18 @@ namespace Managing.Api.Models.Responses
|
|||||||
public decimal TotalPlatformVolumeLast24h { get; set; }
|
public decimal TotalPlatformVolumeLast24h { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Summaries for each agent
|
/// Time filter applied to the data
|
||||||
|
/// </summary>
|
||||||
|
public string TimeFilter { get; set; } = "Total";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Response model containing a list of agent summaries for the agent index
|
||||||
|
/// </summary>
|
||||||
|
public class AgentIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of agent summaries sorted by performance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<AgentSummaryViewModel> AgentSummaries { get; set; } = new List<AgentSummaryViewModel>();
|
public List<AgentSummaryViewModel> AgentSummaries { get; set; } = new List<AgentSummaryViewModel>();
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,10 @@ builder.Services.AddSignalR().AddJsonProtocol();
|
|||||||
builder.Services.AddScoped<IJwtUtils, JwtUtils>();
|
builder.Services.AddScoped<IJwtUtils, JwtUtils>();
|
||||||
|
|
||||||
builder.Services.RegisterApiDependencies(builder.Configuration);
|
builder.Services.RegisterApiDependencies(builder.Configuration);
|
||||||
|
|
||||||
|
// Orleans Configuration
|
||||||
|
builder.Host.ConfigureOrleans(builder.Configuration, builder.Environment.IsProduction());
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddOpenApiDocument(document =>
|
builder.Services.AddOpenApiDocument(document =>
|
||||||
{
|
{
|
||||||
@@ -283,4 +287,6 @@ app.UseEndpoints(endpoints =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -39,5 +39,7 @@
|
|||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"WorkerBotManager": true,
|
"WorkerBotManager": true,
|
||||||
"WorkerBalancesTracking": false,
|
"WorkerBalancesTracking": false,
|
||||||
"WorkerNotifyBundleBacktest": true
|
"WorkerNotifyBundleBacktest": false,
|
||||||
|
"KAIGEN_SECRET_KEY": "KaigenXCowchain",
|
||||||
|
"KAIGEN_CREDITS_ENABLED": false
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
||||||
},
|
},
|
||||||
"Kaigen": {
|
"Kaigen": {
|
||||||
"BaseUrl": "https://kaigen-back-development.up.railway.app/",
|
"BaseUrl": "https://kaigen-back-development.up.railway.app",
|
||||||
"DebitEndpoint": "/api/credits/debit",
|
"DebitEndpoint": "/api/credits/debit",
|
||||||
"RefundEndpoint": "/api/credits/refund"
|
"RefundEndpoint": "/api/credits/refund"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"BaseUrl": "http://localhost:4111"
|
"BaseUrl": "http://localhost:4111"
|
||||||
},
|
},
|
||||||
"Kaigen": {
|
"Kaigen": {
|
||||||
"BaseUrl": "https://api.kaigen.managing.live",
|
"BaseUrl": "https://kaigen-back-development.up.railway.app",
|
||||||
"DebitEndpoint": "/api/credits/debit",
|
"DebitEndpoint": "/api/credits/debit",
|
||||||
"RefundEndpoint": "/api/credits/refund"
|
"RefundEndpoint": "/api/credits/refund"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Managing.Domain.Backtests;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Users;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain interface for Backtest TradingBot operations.
|
||||||
|
/// This interface extends ITradingBotGrain with backtest-specific functionality.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBacktestTradingBotGrain : IGrainWithGuidKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a complete backtest following the exact pattern of GetBacktestingResult from Backtester.cs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">The trading bot configuration for this backtest</param>
|
||||||
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
|
/// <param name="user">The user running the backtest (optional, required for saving)</param>
|
||||||
|
/// <param name="save">Whether to save the backtest results</param>
|
||||||
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
|
/// <param name="requestId">The request ID to associate with this backtest</param>
|
||||||
|
/// <param name="metadata">Additional metadata to associate with this backtest</param>
|
||||||
|
/// <returns>The complete backtest result</returns>
|
||||||
|
Task<LightBacktest> RunBacktestAsync(TradingBotConfig config, List<Candle> candles, User user = null, bool save = false, bool withCandles = false, string requestId = null, object metadata = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current backtest progress
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Backtest progress information</returns>
|
||||||
|
Task<BacktestProgress> GetBacktestProgressAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the progress of a backtest
|
||||||
|
/// </summary>
|
||||||
|
public class BacktestProgress
|
||||||
|
{
|
||||||
|
public bool IsInitialized { get; set; }
|
||||||
|
public int TotalCandles { get; set; }
|
||||||
|
public int ProcessedCandles { get; set; }
|
||||||
|
public double ProgressPercentage { get; set; }
|
||||||
|
public bool IsComplete { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using Managing.Application.Abstractions.Models;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Orleans;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain interface for TradingBot operations.
|
||||||
|
/// This interface defines the distributed, async operations available for trading bots.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITradingBotGrain : IGrainWithGuidKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the trading bot asynchronously
|
||||||
|
/// </summary>
|
||||||
|
Task StartAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the trading bot asynchronously
|
||||||
|
/// </summary>
|
||||||
|
Task StopAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current status of the trading bot
|
||||||
|
/// </summary>
|
||||||
|
Task<BotStatus> GetStatusAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current configuration of the trading bot
|
||||||
|
/// </summary>
|
||||||
|
Task<TradingBotConfig> GetConfigurationAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the trading bot configuration
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfig">The new configuration to apply</param>
|
||||||
|
/// <returns>True if the configuration was successfully updated</returns>
|
||||||
|
Task<bool> UpdateConfigurationAsync(TradingBotConfig newConfig);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manually opens a position in the specified direction
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="direction">The direction of the trade (Long/Short)</param>
|
||||||
|
/// <returns>The created Position object</returns>
|
||||||
|
Task<Position> OpenPositionManuallyAsync(TradeDirection direction);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the bot between watch-only and trading mode
|
||||||
|
/// </summary>
|
||||||
|
Task ToggleIsForWatchOnlyAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets comprehensive bot data including positions, signals, and performance metrics
|
||||||
|
/// </summary>
|
||||||
|
Task<TradingBotResponse> GetBotDataAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a bot backup into the grain state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backup">The bot backup to load</param>
|
||||||
|
Task LoadBackupAsync(BotBackup backup);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces a backup save of the current bot state
|
||||||
|
/// </summary>
|
||||||
|
Task SaveBackupAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current profit and loss for the bot
|
||||||
|
/// </summary>
|
||||||
|
Task<decimal> GetProfitAndLossAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current win rate percentage for the bot
|
||||||
|
/// </summary>
|
||||||
|
Task<int> GetWinRateAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bot's execution count (number of Run cycles completed)
|
||||||
|
/// </summary>
|
||||||
|
Task<long> GetExecutionCountAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bot's startup time
|
||||||
|
/// </summary>
|
||||||
|
Task<DateTime> GetStartupTimeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bot's creation date
|
||||||
|
/// </summary>
|
||||||
|
Task<DateTime> GetCreateDateAsync();
|
||||||
|
}
|
||||||
@@ -11,4 +11,8 @@
|
|||||||
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="9.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Orleans;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Response model for trading bot data.
|
||||||
|
/// Used to return comprehensive bot information via Orleans grains.
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class TradingBotResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Bot identifier
|
||||||
|
/// </summary>
|
||||||
|
[Id(0)]
|
||||||
|
public string Identifier { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot display name
|
||||||
|
/// </summary>
|
||||||
|
[Id(1)]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current bot status
|
||||||
|
/// </summary>
|
||||||
|
[Id(2)]
|
||||||
|
public BotStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot configuration
|
||||||
|
/// </summary>
|
||||||
|
[Id(3)]
|
||||||
|
public TradingBotConfig Config { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trading positions
|
||||||
|
/// </summary>
|
||||||
|
[Id(4)]
|
||||||
|
public List<Position> Positions { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trading signals
|
||||||
|
/// </summary>
|
||||||
|
[Id(5)]
|
||||||
|
public List<LightSignal> Signals { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wallet balance history
|
||||||
|
/// </summary>
|
||||||
|
[Id(6)]
|
||||||
|
public Dictionary<DateTime, decimal> WalletBalances { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current profit and loss
|
||||||
|
/// </summary>
|
||||||
|
[Id(7)]
|
||||||
|
public decimal ProfitAndLoss { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Win rate percentage
|
||||||
|
/// </summary>
|
||||||
|
[Id(8)]
|
||||||
|
public int WinRate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execution count
|
||||||
|
/// </summary>
|
||||||
|
[Id(9)]
|
||||||
|
public long ExecutionCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Startup time
|
||||||
|
/// </summary>
|
||||||
|
[Id(10)]
|
||||||
|
public DateTime StartupTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creation date
|
||||||
|
/// </summary>
|
||||||
|
[Id(11)]
|
||||||
|
public DateTime CreateDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current balance
|
||||||
|
/// </summary>
|
||||||
|
[Id(12)]
|
||||||
|
public decimal CurrentBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of active positions
|
||||||
|
/// </summary>
|
||||||
|
[Id(13)]
|
||||||
|
public int ActivePositionsCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last execution time
|
||||||
|
/// </summary>
|
||||||
|
[Id(14)]
|
||||||
|
public DateTime LastExecution { get; set; }
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a trading bot backtest with the specified configuration and date range.
|
/// Runs a trading bot backtest with the specified configuration and date range.
|
||||||
/// Automatically handles different bot types based on config.BotType.
|
/// Automatically handles different bot types based on config.BotType.
|
||||||
|
/// Returns a LightBacktest for efficient Orleans serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||||
/// <param name="startDate">The start date for the backtest</param>
|
/// <param name="startDate">The start date for the backtest</param>
|
||||||
@@ -19,8 +20,8 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
||||||
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
||||||
/// <returns>The backtest results</returns>
|
/// <returns>The lightweight backtest results</returns>
|
||||||
Task<Backtest> RunTradingBotBacktest(
|
Task<LightBacktest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
DateTime startDate,
|
DateTime startDate,
|
||||||
DateTime endDate,
|
DateTime endDate,
|
||||||
@@ -33,6 +34,7 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a trading bot backtest with pre-loaded candles.
|
/// Runs a trading bot backtest with pre-loaded candles.
|
||||||
/// Automatically handles different bot types based on config.BotType.
|
/// Automatically handles different bot types based on config.BotType.
|
||||||
|
/// Returns a LightBacktest for efficient Orleans serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||||
/// <param name="candles">The candles to use for backtesting</param>
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
@@ -40,8 +42,8 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
||||||
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
||||||
/// <returns>The backtest results</returns>
|
/// <returns>The lightweight backtest results</returns>
|
||||||
Task<Backtest> RunTradingBotBacktest(
|
Task<LightBacktest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
List<Candle> candles,
|
||||||
User user = null,
|
User user = null,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using Managing.Application.Bots.Base;
|
|||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Application.ManageBot;
|
using Managing.Application.ManageBot;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
@@ -51,7 +50,7 @@ namespace Managing.Application.Tests
|
|||||||
_tradingService.Object,
|
_tradingService.Object,
|
||||||
botService, backupBotService);
|
botService, backupBotService);
|
||||||
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
||||||
scenarioService, _accountService.Object, messengerService, kaigenService, hubContext);
|
scenarioService, _accountService.Object, messengerService, kaigenService, hubContext, null);
|
||||||
_elapsedTimes = new List<double>();
|
_elapsedTimes = new List<double>();
|
||||||
|
|
||||||
// Initialize cross-platform file paths
|
// Initialize cross-platform file paths
|
||||||
@@ -78,7 +77,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = MoneyManagement,
|
MoneyManagement = MoneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -128,7 +127,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = MoneyManagement,
|
MoneyManagement = MoneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -177,7 +176,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -194,7 +193,7 @@ namespace Managing.Application.Tests
|
|||||||
// Act
|
// Act
|
||||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
||||||
DateTime.UtcNow, null, false, false);
|
DateTime.UtcNow, null, false, false);
|
||||||
WriteCsvReport(backtestResult.GetStringReport());
|
// WriteCsvReport(backtestResult.GetStringReport());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(backtestResult.FinalPnl > 0);
|
Assert.True(backtestResult.FinalPnl > 0);
|
||||||
@@ -234,10 +233,10 @@ namespace Managing.Application.Tests
|
|||||||
if (candles == null || candles.Count == 0)
|
if (candles == null || candles.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Parallel.For(periodRange[0], periodRange[1], options, i =>
|
Parallel.For((long)periodRange[0], periodRange[1], options, i =>
|
||||||
{
|
{
|
||||||
var scenario = new Scenario("ScalpingScenario");
|
var scenario = new Scenario("ScalpingScenario");
|
||||||
var strategy = ScenarioHelpers.BuildIndicator(indicatorType, "RsiDiv", period: i);
|
var strategy = ScenarioHelpers.BuildIndicator(indicatorType, "RsiDiv", period: (int)i);
|
||||||
scenario.AddIndicator(strategy);
|
scenario.AddIndicator(strategy);
|
||||||
|
|
||||||
// -0.5 to -5
|
// -0.5 to -5
|
||||||
@@ -267,7 +266,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -285,7 +284,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -304,10 +303,10 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
if (backtestResult.FinalPnl > 0
|
if (backtestResult.FinalPnl > 0
|
||||||
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
|
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
|
||||||
&& backtestResult.Statistics.MaxDrawdown < 3)
|
&& backtestResult.Score < 3)
|
||||||
{
|
{
|
||||||
var currentResult = new Tuple<string, int, decimal, decimal, decimal, decimal>(
|
var currentResult = new Tuple<string, int, decimal, decimal, decimal, decimal>(
|
||||||
ticker.ToString(), i,
|
ticker.ToString(), (int)i,
|
||||||
backtestResult.FinalPnl, s, t,
|
backtestResult.FinalPnl, s, t,
|
||||||
backtestResult.GrowthPercentage - backtestResult.HodlPercentage);
|
backtestResult.GrowthPercentage - backtestResult.HodlPercentage);
|
||||||
result.Add(currentResult);
|
result.Add(currentResult);
|
||||||
@@ -407,7 +406,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -425,7 +424,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -442,8 +441,7 @@ namespace Managing.Application.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (backtestResult.FinalPnl > 0
|
if (backtestResult.FinalPnl > 0
|
||||||
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
|
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30)
|
||||||
&& backtestResult.Statistics.MaxDrawdown < 3)
|
|
||||||
{
|
{
|
||||||
var currentResult = new Tuple<string, decimal, decimal, decimal, decimal>(
|
var currentResult = new Tuple<string, decimal, decimal, decimal, decimal>(
|
||||||
ticker.ToString(),
|
ticker.ToString(),
|
||||||
@@ -661,7 +659,7 @@ namespace Managing.Application.Tests
|
|||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
MoneyManagement = standardMoneyManagement,
|
MoneyManagement = standardMoneyManagement,
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
BotTradingBalance = 1000,
|
BotTradingBalance = 1000,
|
||||||
@@ -679,24 +677,6 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
|
|
||||||
var scoringParams = new BacktestScoringParams(
|
|
||||||
sharpeRatio: (double)(backtestResult.Statistics?.SharpeRatio ?? 0),
|
|
||||||
growthPercentage: (double)backtestResult.GrowthPercentage,
|
|
||||||
hodlPercentage: (double)backtestResult.HodlPercentage,
|
|
||||||
winRate: backtestResult.WinRate / 100.0, // Convert percentage to decimal
|
|
||||||
totalPnL: (double)backtestResult.FinalPnl,
|
|
||||||
fees: (double)backtestResult.Fees,
|
|
||||||
tradeCount: backtestResult.Positions?.Count ?? 0,
|
|
||||||
maxDrawdownRecoveryTime: backtestResult.Statistics?.MaxDrawdownRecoveryTime ?? TimeSpan.Zero,
|
|
||||||
maxDrawdown: backtestResult.Statistics?.MaxDrawdown ?? 0,
|
|
||||||
initialBalance: config.BotTradingBalance,
|
|
||||||
tradingBalance: config.BotTradingBalance,
|
|
||||||
startDate: backtestResult.StartDate,
|
|
||||||
endDate: backtestResult.EndDate,
|
|
||||||
timeframe: config.Timeframe,
|
|
||||||
moneyManagement: config.MoneyManagement
|
|
||||||
);
|
|
||||||
|
|
||||||
var scenarioResult = new ScenarioBacktestResult
|
var scenarioResult = new ScenarioBacktestResult
|
||||||
{
|
{
|
||||||
ScenarioName = scenario.Name,
|
ScenarioName = scenario.Name,
|
||||||
@@ -708,14 +688,13 @@ namespace Managing.Application.Tests
|
|||||||
GrowthPercentage = backtestResult.GrowthPercentage,
|
GrowthPercentage = backtestResult.GrowthPercentage,
|
||||||
HodlPercentage = backtestResult.HodlPercentage,
|
HodlPercentage = backtestResult.HodlPercentage,
|
||||||
OutperformanceVsHodl = backtestResult.GrowthPercentage - backtestResult.HodlPercentage,
|
OutperformanceVsHodl = backtestResult.GrowthPercentage - backtestResult.HodlPercentage,
|
||||||
MaxDrawdown = (double)(backtestResult.Statistics?.MaxDrawdown ?? 0),
|
MaxDrawdown = (double)(backtestResult.MaxDrawdown ?? 0),
|
||||||
TotalTrades = backtestResult.Positions?.Count ?? 0,
|
SharpeRatio = (double)(backtestResult.SharpeRatio ?? 0),
|
||||||
SharpeRatio = (double)(backtestResult.Statistics?.SharpeRatio ?? 0),
|
|
||||||
ExecutionTime = timer.Elapsed.TotalSeconds,
|
ExecutionTime = timer.Elapsed.TotalSeconds,
|
||||||
StopLoss = standardMoneyManagement.StopLoss,
|
StopLoss = standardMoneyManagement.StopLoss,
|
||||||
TakeProfit = standardMoneyManagement.TakeProfit,
|
TakeProfit = standardMoneyManagement.TakeProfit,
|
||||||
Leverage = standardMoneyManagement.Leverage,
|
Leverage = standardMoneyManagement.Leverage,
|
||||||
Score = BacktestScorer.CalculateTotalScore(scoringParams)
|
Score = backtestResult.Score,
|
||||||
};
|
};
|
||||||
|
|
||||||
results.Add(scenarioResult);
|
results.Add(scenarioResult);
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0"/>
|
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.5"/>
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageReference Include="Microsoft.TestPlatform.AdapterUtilities" Version="17.9.0"/>
|
<PackageReference Include="Microsoft.TestPlatform.AdapterUtilities" Version="17.9.0" />
|
||||||
<PackageReference Include="Moq" Version="4.20.70"/>
|
<PackageReference Include="Moq" Version="4.20.70" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1"/>
|
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1"/>
|
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.8.0"/>
|
<PackageReference Include="xunit" Version="2.8.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@@ -24,17 +24,17 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Api\Managing.Api.csproj"/>
|
<ProjectReference Include="..\Managing.Api\Managing.Api.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj"/>
|
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj"/>
|
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
|
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Tests\Managing.Infrastructure.Tests.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Tests\Managing.Infrastructure.Tests.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\"/>
|
<Folder Include="Data\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Backtesting;
|
using Managing.Application.Backtesting;
|
||||||
using Managing.Application.Bots;
|
using Managing.Application.Bots;
|
||||||
@@ -8,16 +7,15 @@ using Managing.Infrastructure.Databases.InfluxDb;
|
|||||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
||||||
using Managing.Infrastructure.Evm;
|
using Managing.Infrastructure.Evm;
|
||||||
using Managing.Infrastructure.Evm.Abstractions;
|
using Managing.Infrastructure.Evm.Abstractions;
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
|
||||||
using Managing.Infrastructure.Evm.Services;
|
using Managing.Infrastructure.Evm.Services;
|
||||||
using Managing.Infrastructure.Evm.Subgraphs;
|
using Managing.Infrastructure.Evm.Subgraphs;
|
||||||
using Managing.Infrastructure.Exchanges;
|
using Managing.Infrastructure.Exchanges;
|
||||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||||
using Managing.Infrastructure.Exchanges.Exchanges;
|
using Managing.Infrastructure.Exchanges.Exchanges;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Nethereum.Web3;
|
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Tests
|
namespace Managing.Application.Tests
|
||||||
@@ -26,7 +24,7 @@ namespace Managing.Application.Tests
|
|||||||
{
|
{
|
||||||
public static IExchangeService GetExchangeService()
|
public static IExchangeService GetExchangeService()
|
||||||
{
|
{
|
||||||
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
|
ILoggerFactory loggerFactory = new NullLoggerFactory();
|
||||||
|
|
||||||
var ChainlinkGmx = new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx));
|
var ChainlinkGmx = new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx));
|
||||||
var Chainlink = new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice));
|
var Chainlink = new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice));
|
||||||
@@ -53,23 +51,23 @@ namespace Managing.Application.Tests
|
|||||||
exchangeProcessors);
|
exchangeProcessors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ILogger<TradingBot> CreateTradingBotLogger()
|
public static ILogger<TradingBotBase> CreateTradingBotLogger()
|
||||||
{
|
{
|
||||||
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
|
ILoggerFactory loggerFactory = new NullLoggerFactory();
|
||||||
|
|
||||||
return loggerFactory.CreateLogger<TradingBot>();
|
return loggerFactory.CreateLogger<TradingBotBase>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ILogger<Backtester> CreateBacktesterLogger()
|
public static ILogger<Backtester> CreateBacktesterLogger()
|
||||||
{
|
{
|
||||||
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
|
ILoggerFactory loggerFactory = new NullLoggerFactory();
|
||||||
|
|
||||||
return loggerFactory.CreateLogger<Backtester>();
|
return loggerFactory.CreateLogger<Backtester>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ILogger<CandleRepository> CreateCandleRepositoryLogger()
|
public static ILogger<CandleRepository> CreateCandleRepositoryLogger()
|
||||||
{
|
{
|
||||||
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
|
ILoggerFactory loggerFactory = new NullLoggerFactory();
|
||||||
|
|
||||||
return loggerFactory.CreateLogger<CandleRepository>();
|
return loggerFactory.CreateLogger<CandleRepository>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,17 +222,14 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map Scenario
|
// Map Scenario
|
||||||
Scenario scenario = null;
|
LightScenario scenario = null;
|
||||||
if (runBacktestRequest.Config.Scenario != null)
|
if (runBacktestRequest.Config.Scenario != null)
|
||||||
{
|
{
|
||||||
var sReq = runBacktestRequest.Config.Scenario;
|
var sReq = runBacktestRequest.Config.Scenario;
|
||||||
scenario = new Scenario(sReq.Name, sReq.LoopbackPeriod)
|
scenario = new LightScenario(sReq.Name, sReq.LoopbackPeriod);
|
||||||
{
|
|
||||||
User = null // No user context in worker
|
|
||||||
};
|
|
||||||
foreach (var indicatorRequest in sReq.Indicators)
|
foreach (var indicatorRequest in sReq.Indicators)
|
||||||
{
|
{
|
||||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
var indicator = new LightIndicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||||
{
|
{
|
||||||
SignalType = indicatorRequest.SignalType,
|
SignalType = indicatorRequest.SignalType,
|
||||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||||
@@ -244,7 +241,6 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
||||||
StochPeriods = indicatorRequest.StochPeriods,
|
StochPeriods = indicatorRequest.StochPeriods,
|
||||||
CyclePeriods = indicatorRequest.CyclePeriods,
|
CyclePeriods = indicatorRequest.CyclePeriods,
|
||||||
User = null // No user context in worker
|
|
||||||
};
|
};
|
||||||
scenario.AddIndicator(indicator);
|
scenario.AddIndicator(indicator);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
|
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
|
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,8 @@ public class StatisticService : IStatisticService
|
|||||||
await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
|
await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<LightSignal>> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe)
|
private async Task<List<LightSignal>> GetSignals(Account account, Scenario scenario, Ticker ticker,
|
||||||
|
Timeframe timeframe)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -305,7 +306,11 @@ public class StatisticService : IStatisticService
|
|||||||
false,
|
false,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
return backtest.Signals;
|
// Note: LightBacktest doesn't contain signals data, so we return an empty list
|
||||||
|
// The full signals data would need to be retrieved from the database using the backtest ID
|
||||||
|
_logger.LogWarning(
|
||||||
|
"GetSignals called but LightBacktest doesn't contain signals data. Returning empty list.");
|
||||||
|
return new List<LightSignal>();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Workflows;
|
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions
|
namespace Managing.Application.Abstractions
|
||||||
{
|
{
|
||||||
public interface IBotFactory
|
public interface IBotFactory
|
||||||
{
|
{
|
||||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a trading bot using the unified TradingBot class
|
/// Creates a trading bot using the unified TradingBot class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ public interface IBotService
|
|||||||
/// <returns>ITradingBot instance configured for backtesting</returns>
|
/// <returns>ITradingBot instance configured for backtesting</returns>
|
||||||
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
|
||||||
|
|
||||||
// Legacy methods - these will use TradingBot internally but maintain backward compatibility
|
|
||||||
Task<ITradingBot> CreateScalpingBot(TradingBotConfig config);
|
|
||||||
Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config);
|
|
||||||
Task<ITradingBot> CreateFlippingBot(TradingBotConfig config);
|
|
||||||
Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config);
|
|
||||||
|
|
||||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||||
Task<string> StopBot(string botName);
|
Task<string> StopBot(string botName);
|
||||||
Task<bool> DeleteBot(string botName);
|
Task<bool> DeleteBot(string botName);
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ namespace Managing.Application.Abstractions
|
|||||||
|
|
||||||
Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
|
Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
|
||||||
|
|
||||||
Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods,
|
Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
||||||
|
int? slowPeriods,
|
||||||
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
|
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
|
||||||
|
|
||||||
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
|
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
|
||||||
@@ -49,8 +50,11 @@ namespace Managing.Application.Abstractions
|
|||||||
Task<bool> DeleteScenariosByUser(User user);
|
Task<bool> DeleteScenariosByUser(User user);
|
||||||
Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
|
Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
|
||||||
|
|
||||||
Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
|
||||||
|
int? fastPeriods,
|
||||||
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods,
|
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods,
|
||||||
int? cyclePeriods);
|
int? cyclePeriods);
|
||||||
|
|
||||||
|
Task<Scenario> GetScenarioByNameAndUserAsync(string scenarioName, User user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Bots;
|
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Core.FixedSizedQueue;
|
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Strategies.Base;
|
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Managing.Domain.Workflows;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -32,6 +27,7 @@ namespace Managing.Application.Backtesting
|
|||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private readonly IKaigenService _kaigenService;
|
private readonly IKaigenService _kaigenService;
|
||||||
private readonly IHubContext<BacktestHub> _hubContext;
|
private readonly IHubContext<BacktestHub> _hubContext;
|
||||||
|
private readonly IGrainFactory _grainFactory;
|
||||||
|
|
||||||
public Backtester(
|
public Backtester(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
@@ -42,7 +38,8 @@ namespace Managing.Application.Backtesting
|
|||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IKaigenService kaigenService,
|
IKaigenService kaigenService,
|
||||||
IHubContext<BacktestHub> hubContext)
|
IHubContext<BacktestHub> hubContext,
|
||||||
|
IGrainFactory grainFactory)
|
||||||
{
|
{
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_botFactory = botFactory;
|
_botFactory = botFactory;
|
||||||
@@ -53,19 +50,7 @@ namespace Managing.Application.Backtesting
|
|||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
_kaigenService = kaigenService;
|
_kaigenService = kaigenService;
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
}
|
_grainFactory = grainFactory;
|
||||||
|
|
||||||
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
|
|
||||||
{
|
|
||||||
var simplebot = _botFactory.CreateSimpleBot("scenario", workflow);
|
|
||||||
Backtest result = null;
|
|
||||||
if (save && result != null)
|
|
||||||
{
|
|
||||||
// Simple bot backtest not implemented yet, would need user
|
|
||||||
// _backtestRepository.InsertBacktestForUser(null, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,8 +65,8 @@ namespace Managing.Application.Backtesting
|
|||||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
||||||
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
||||||
/// <returns>The backtest results</returns>
|
/// <returns>The lightweight backtest results</returns>
|
||||||
public async Task<Backtest> RunTradingBotBacktest(
|
public async Task<LightBacktestResponse> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
DateTime startDate,
|
DateTime startDate,
|
||||||
DateTime endDate,
|
DateTime endDate,
|
||||||
@@ -114,25 +99,7 @@ namespace Managing.Application.Backtesting
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
||||||
|
return await RunBacktestWithCandles(config, candles, user, save, withCandles, requestId, metadata);
|
||||||
var result = await RunBacktestWithCandles(config, candles, user, withCandles, requestId, metadata);
|
|
||||||
|
|
||||||
// Set start and end dates
|
|
||||||
result.StartDate = startDate;
|
|
||||||
result.EndDate = endDate;
|
|
||||||
|
|
||||||
// Ensure RequestId is set - required for PostgreSQL NOT NULL constraint
|
|
||||||
if (string.IsNullOrEmpty(result.RequestId))
|
|
||||||
{
|
|
||||||
result.RequestId = Guid.NewGuid().ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (save && user != null)
|
|
||||||
{
|
|
||||||
_backtestRepository.InsertBacktestForUser(user, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -172,8 +139,10 @@ namespace Managing.Application.Backtesting
|
|||||||
/// <param name="candles">The candles to use for backtesting</param>
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
/// <param name="user">The user running the backtest (optional)</param>
|
/// <param name="user">The user running the backtest (optional)</param>
|
||||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
/// <returns>The backtest results</returns>
|
/// <param name="requestId">The request ID to associate with this backtest (optional)</param>
|
||||||
public async Task<Backtest> RunTradingBotBacktest(
|
/// <param name="metadata">Additional metadata to associate with this backtest (optional)</param>
|
||||||
|
/// <returns>The lightweight backtest results</returns>
|
||||||
|
public async Task<LightBacktestResponse> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
List<Candle> candles,
|
||||||
User user = null,
|
User user = null,
|
||||||
@@ -181,43 +150,49 @@ namespace Managing.Application.Backtesting
|
|||||||
string requestId = null,
|
string requestId = null,
|
||||||
object metadata = null)
|
object metadata = null)
|
||||||
{
|
{
|
||||||
return await RunBacktestWithCandles(config, candles, user, withCandles, requestId, metadata);
|
return await RunBacktestWithCandles(config, candles, user, false, withCandles, requestId, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Core backtesting logic - handles the actual backtest execution with pre-loaded candles
|
/// Core backtesting logic - handles the actual backtest execution with pre-loaded candles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<Backtest> RunBacktestWithCandles(
|
private async Task<LightBacktestResponse> RunBacktestWithCandles(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
List<Candle> candles,
|
||||||
User user = null,
|
User user = null,
|
||||||
|
bool save = false,
|
||||||
bool withCandles = false,
|
bool withCandles = false,
|
||||||
string requestId = null,
|
string requestId = null,
|
||||||
object metadata = null)
|
object metadata = null)
|
||||||
{
|
{
|
||||||
var tradingBot = await _botFactory.CreateBacktestTradingBot(config);
|
// Ensure this is a backtest configuration
|
||||||
|
if (!config.IsForBacktest)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Backtest configuration must have IsForBacktest set to true");
|
||||||
|
}
|
||||||
|
|
||||||
// Scenario and indicators should already be loaded in constructor by BotService
|
// Validate that scenario and indicators are properly loaded
|
||||||
// This is just a validation check to ensure everything loaded properly
|
if (config.Scenario == null && string.IsNullOrEmpty(config.ScenarioName))
|
||||||
if (tradingBot is TradingBot bot && !bot.Indicators.Any())
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"No indicators were loaded for scenario '{config.ScenarioName ?? config.Scenario?.Name}'. " +
|
"Backtest configuration must include either Scenario object or ScenarioName");
|
||||||
"This indicates a problem with scenario loading.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tradingBot.User = user;
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
tradingBot.Account = await GetAccountFromConfig(config);
|
|
||||||
|
|
||||||
var result =
|
|
||||||
await GetBacktestingResult(config, tradingBot, candles, user, withCandles, requestId, metadata);
|
|
||||||
|
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
result.User = user;
|
var fullScenario = await _scenarioService.GetScenarioByNameAndUserAsync(config.ScenarioName, user);
|
||||||
|
config.Scenario = LightScenario.FromScenario(fullScenario);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
// Create a clean copy of the config to avoid Orleans serialization issues
|
||||||
|
var cleanConfig = CreateCleanConfigForOrleans(config);
|
||||||
|
|
||||||
|
// Create Orleans grain for backtesting
|
||||||
|
var backtestGrain = _grainFactory.GetGrain<IBacktestTradingBotGrain>(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Run the backtest using the Orleans grain and return LightBacktest directly
|
||||||
|
return await backtestGrain.RunBacktestAsync(cleanConfig, candles, user, save, withCandles, requestId,
|
||||||
|
metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
|
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
|
||||||
@@ -237,128 +212,16 @@ namespace Managing.Application.Backtesting
|
|||||||
return candles;
|
return candles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Backtest> GetBacktestingResult(
|
|
||||||
TradingBotConfig config,
|
/// <summary>
|
||||||
ITradingBot bot,
|
/// Creates a clean copy of the trading bot config for Orleans serialization
|
||||||
List<Candle> candles,
|
/// Uses LightScenario and LightIndicator to avoid FixedSizeQueue serialization issues
|
||||||
User user = null,
|
/// </summary>
|
||||||
bool withCandles = false,
|
private TradingBotConfig CreateCleanConfigForOrleans(TradingBotConfig originalConfig)
|
||||||
string requestId = null,
|
|
||||||
object metadata = null)
|
|
||||||
{
|
{
|
||||||
if (candles == null || candles.Count == 0)
|
// Since we're now using LightScenario in TradingBotConfig, we can just return the original config
|
||||||
{
|
// The conversion to LightScenario is already done when loading the scenario
|
||||||
throw new Exception("No candle to backtest");
|
return originalConfig;
|
||||||
}
|
|
||||||
|
|
||||||
var totalCandles = candles.Count;
|
|
||||||
var currentCandle = 0;
|
|
||||||
var lastLoggedPercentage = 0;
|
|
||||||
|
|
||||||
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
|
||||||
totalCandles, config.Ticker, config.Timeframe);
|
|
||||||
|
|
||||||
bot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
|
|
||||||
|
|
||||||
foreach (var candle in candles)
|
|
||||||
{
|
|
||||||
bot.OptimizedCandles.Enqueue(candle);
|
|
||||||
bot.Candles.Add(candle);
|
|
||||||
await bot.Run();
|
|
||||||
|
|
||||||
currentCandle++;
|
|
||||||
|
|
||||||
// Check if wallet balance fell below 10 USDC and break if so
|
|
||||||
var currentWalletBalance = bot.WalletBalances.Values.LastOrDefault();
|
|
||||||
if (currentWalletBalance < 10m)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(
|
|
||||||
"Backtest stopped early: Wallet balance fell below 10 USDC (Current: {CurrentBalance:F2} USDC) at candle {CurrentCandle}/{TotalCandles} from {CandleDate}",
|
|
||||||
currentWalletBalance, currentCandle, totalCandles, candle.Date.ToString("yyyy-MM-dd HH:mm"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log progress every 10% or every 1000 candles, whichever comes first
|
|
||||||
var currentPercentage = (int)((double)currentCandle / totalCandles * 100);
|
|
||||||
var shouldLog = currentPercentage >= lastLoggedPercentage + 10 ||
|
|
||||||
currentCandle % 1000 == 0 ||
|
|
||||||
currentCandle == totalCandles;
|
|
||||||
|
|
||||||
if (shouldLog && currentPercentage > lastLoggedPercentage)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Backtest progress: {CurrentCandle}/{TotalCandles} ({Percentage}%) - Processing candle from {CandleDate}",
|
|
||||||
currentCandle, totalCandles, currentPercentage, candle.Date.ToString("yyyy-MM-dd HH:mm"));
|
|
||||||
lastLoggedPercentage = currentPercentage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Backtest processing completed. Calculating final results...");
|
|
||||||
|
|
||||||
bot.Candles = new HashSet<Candle>(candles);
|
|
||||||
|
|
||||||
// Only calculate indicators values if withCandles is true
|
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues = null;
|
|
||||||
if (withCandles)
|
|
||||||
{
|
|
||||||
indicatorsValues = GetIndicatorsValues(bot.Config.Scenario.Indicators, candles);
|
|
||||||
}
|
|
||||||
|
|
||||||
var finalPnl = bot.GetProfitAndLoss();
|
|
||||||
var winRate = bot.GetWinRate();
|
|
||||||
var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
|
|
||||||
var growthPercentage =
|
|
||||||
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
|
|
||||||
var hodlPercentage = TradingHelpers.GetHodlPercentage(candles[0], candles.Last());
|
|
||||||
|
|
||||||
var fees = bot.GetTotalFees();
|
|
||||||
var scoringParams = new BacktestScoringParams(
|
|
||||||
sharpeRatio: (double)stats.SharpeRatio,
|
|
||||||
growthPercentage: (double)growthPercentage,
|
|
||||||
hodlPercentage: (double)hodlPercentage,
|
|
||||||
winRate: winRate,
|
|
||||||
totalPnL: (double)finalPnl,
|
|
||||||
fees: (double)fees,
|
|
||||||
tradeCount: bot.Positions.Count,
|
|
||||||
maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime,
|
|
||||||
maxDrawdown: stats.MaxDrawdown,
|
|
||||||
initialBalance: bot.WalletBalances.FirstOrDefault().Value,
|
|
||||||
tradingBalance: config.BotTradingBalance,
|
|
||||||
startDate: candles[0].Date,
|
|
||||||
endDate: candles.Last().Date,
|
|
||||||
timeframe: config.Timeframe,
|
|
||||||
moneyManagement: config.MoneyManagement
|
|
||||||
);
|
|
||||||
|
|
||||||
var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
|
|
||||||
|
|
||||||
// Create backtest result with conditional candles and indicators values
|
|
||||||
var result = new Backtest(config, bot.Positions, bot.Signals.ToList(),
|
|
||||||
withCandles ? candles : new List<Candle>())
|
|
||||||
{
|
|
||||||
FinalPnl = finalPnl,
|
|
||||||
WinRate = winRate,
|
|
||||||
GrowthPercentage = growthPercentage,
|
|
||||||
HodlPercentage = hodlPercentage,
|
|
||||||
Fees = fees,
|
|
||||||
WalletBalances = bot.WalletBalances.ToList(),
|
|
||||||
Statistics = stats,
|
|
||||||
IndicatorsValues = withCandles
|
|
||||||
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
|
|
||||||
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
|
||||||
Score = scoringResult.Score,
|
|
||||||
ScoreMessage = scoringResult.GenerateSummaryMessage(),
|
|
||||||
Id = Guid.NewGuid().ToString(),
|
|
||||||
RequestId = requestId,
|
|
||||||
Metadata = metadata,
|
|
||||||
StartDate = candles.FirstOrDefault()!.OpenTime,
|
|
||||||
EndDate = candles.LastOrDefault()!.OpenTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send notification if backtest meets criteria
|
|
||||||
await SendBacktestNotificationIfCriteriaMet(result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
|
private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
|
||||||
@@ -376,56 +239,6 @@ namespace Managing.Application.Backtesting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<IndicatorType, IndicatorsResultBase> AggregateValues(
|
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues,
|
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> botStrategiesValues)
|
|
||||||
{
|
|
||||||
// Foreach strategy type, only retrieve the values where the strategy is not present already in the bot
|
|
||||||
// Then, add the values to the bot values
|
|
||||||
|
|
||||||
var result = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
|
||||||
foreach (var indicator in indicatorsValues)
|
|
||||||
{
|
|
||||||
// if (!botStrategiesValues.ContainsKey(strategy.Key))
|
|
||||||
// {
|
|
||||||
// result[strategy.Key] = strategy.Value;
|
|
||||||
// }else
|
|
||||||
// {
|
|
||||||
// result[strategy.Key] = botStrategiesValues[strategy.Key];
|
|
||||||
// }
|
|
||||||
|
|
||||||
result[indicator.Key] = indicator.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<IndicatorType, IndicatorsResultBase> GetIndicatorsValues(List<Indicator> indicators,
|
|
||||||
List<Candle> candles)
|
|
||||||
{
|
|
||||||
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
|
||||||
var fixedCandles = new FixedSizeQueue<Candle>(10000);
|
|
||||||
foreach (var candle in candles)
|
|
||||||
{
|
|
||||||
fixedCandles.Enqueue(candle);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var indicator in indicators)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var s = ScenarioHelpers.BuildIndicator(indicator, 10000);
|
|
||||||
s.Candles = fixedCandles;
|
|
||||||
indicatorsValues[indicator.Type] = s.GetIndicatorValues();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return indicatorsValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> DeleteBacktestAsync(string id)
|
public async Task<bool> DeleteBacktestAsync(string id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.ManageBot;
|
using Managing.Application.ManageBot;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Workflows;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Managing.Application.Bots.Base
|
namespace Managing.Application.Bots.Base
|
||||||
@@ -12,14 +11,14 @@ namespace Managing.Application.Bots.Base
|
|||||||
private readonly IExchangeService _exchangeService;
|
private readonly IExchangeService _exchangeService;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
private readonly ILogger<TradingBotBase> _tradingBotLogger;
|
||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
private readonly IBotService _botService;
|
private readonly IBotService _botService;
|
||||||
private readonly IBackupBotService _backupBotService;
|
private readonly IBackupBotService _backupBotService;
|
||||||
|
|
||||||
public BotFactory(
|
public BotFactory(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
ILogger<TradingBot> tradingBotLogger,
|
ILogger<TradingBotBase> tradingBotLogger,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ITradingService tradingService,
|
ITradingService tradingService,
|
||||||
@@ -35,11 +34,6 @@ namespace Managing.Application.Bots.Base
|
|||||||
_backupBotService = backupBotService;
|
_backupBotService = backupBotService;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
|
|
||||||
{
|
|
||||||
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService, _backupBotService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Delegate to BotService which handles scenario loading properly
|
// Delegate to BotService which handles scenario loading properly
|
||||||
|
|||||||
403
src/Managing.Application/Bots/Grains/BacktestTradingBotGrain.cs
Normal file
403
src/Managing.Application/Bots/Grains/BacktestTradingBotGrain.cs
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
|
using Managing.Application.Abstractions.Models;
|
||||||
|
using Managing.Application.Abstractions.Repositories;
|
||||||
|
using Managing.Core.FixedSizedQueue;
|
||||||
|
using Managing.Domain.Backtests;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Scenarios;
|
||||||
|
using Managing.Domain.Shared.Helpers;
|
||||||
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Orleans.Concurrency;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Bots.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain for backtest trading bot operations.
|
||||||
|
/// Uses composition with TradingBotBase to maintain separation of concerns.
|
||||||
|
/// This grain is stateless and follows the exact pattern of GetBacktestingResult from Backtester.cs.
|
||||||
|
/// </summary>
|
||||||
|
[StatelessWorker]
|
||||||
|
public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
|
||||||
|
{
|
||||||
|
private readonly ILogger<BacktestTradingBotGrain> _logger;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly IBacktestRepository _backtestRepository;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
public BacktestTradingBotGrain(
|
||||||
|
ILogger<BacktestTradingBotGrain> logger,
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
IBacktestRepository backtestRepository)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
_backtestRepository = backtestRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a complete backtest following the exact pattern of GetBacktestingResult from Backtester.cs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">The trading bot configuration for this backtest</param>
|
||||||
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
|
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||||
|
/// <param name="requestId">The request ID to associate with this backtest</param>
|
||||||
|
/// <param name="metadata">Additional metadata to associate with this backtest</param>
|
||||||
|
/// <returns>The complete backtest result</returns>
|
||||||
|
public async Task<LightBacktest> RunBacktestAsync(
|
||||||
|
TradingBotConfig config,
|
||||||
|
List<Candle> candles,
|
||||||
|
User user = null,
|
||||||
|
bool save = false,
|
||||||
|
bool withCandles = false,
|
||||||
|
string requestId = null,
|
||||||
|
object metadata = null)
|
||||||
|
{
|
||||||
|
if (candles == null || candles.Count == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("No candle to backtest");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a fresh TradingBotBase instance for this backtest
|
||||||
|
var tradingBot = await CreateTradingBotInstance(config);
|
||||||
|
tradingBot.Start();
|
||||||
|
|
||||||
|
var totalCandles = candles.Count;
|
||||||
|
var currentCandle = 0;
|
||||||
|
var lastLoggedPercentage = 0;
|
||||||
|
|
||||||
|
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
||||||
|
totalCandles, config.Ticker, config.Timeframe);
|
||||||
|
|
||||||
|
// Initialize wallet balance with first candle
|
||||||
|
tradingBot.WalletBalances.Clear();
|
||||||
|
tradingBot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
|
||||||
|
|
||||||
|
// Process all candles following the exact pattern from GetBacktestingResult
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
tradingBot.OptimizedCandles.Enqueue(candle);
|
||||||
|
tradingBot.Candles.Add(candle);
|
||||||
|
await tradingBot.Run();
|
||||||
|
|
||||||
|
currentCandle++;
|
||||||
|
|
||||||
|
// Check if wallet balance fell below 10 USDC and break if so
|
||||||
|
var currentWalletBalance = tradingBot.WalletBalances.Values.LastOrDefault();
|
||||||
|
if (currentWalletBalance < 10m)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Backtest stopped early: Wallet balance fell below 10 USDC (Current: {CurrentBalance:F2} USDC) at candle {CurrentCandle}/{TotalCandles} from {CandleDate}",
|
||||||
|
currentWalletBalance, currentCandle, totalCandles, candle.Date.ToString("yyyy-MM-dd HH:mm"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log progress every 10% or every 1000 candles, whichever comes first
|
||||||
|
var currentPercentage = (int)((double)currentCandle / totalCandles * 100);
|
||||||
|
var shouldLog = currentPercentage >= lastLoggedPercentage + 10 ||
|
||||||
|
currentCandle % 1000 == 0 ||
|
||||||
|
currentCandle == totalCandles;
|
||||||
|
|
||||||
|
if (shouldLog && currentPercentage > lastLoggedPercentage)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Backtest progress: {CurrentCandle}/{TotalCandles} ({Percentage}%) - Processing candle from {CandleDate}",
|
||||||
|
currentCandle, totalCandles, currentPercentage, candle.Date.ToString("yyyy-MM-dd HH:mm"));
|
||||||
|
lastLoggedPercentage = currentPercentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Backtest processing completed. Calculating final results...");
|
||||||
|
|
||||||
|
// Set all candles for final calculations
|
||||||
|
tradingBot.Candles = new HashSet<Candle>(candles);
|
||||||
|
|
||||||
|
// Only calculate indicators values if withCandles is true
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues = null;
|
||||||
|
if (withCandles)
|
||||||
|
{
|
||||||
|
// Convert LightScenario back to full Scenario for indicator calculations
|
||||||
|
var fullScenario = config.Scenario.ToScenario();
|
||||||
|
indicatorsValues = GetIndicatorsValues(fullScenario.Indicators, candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate final results following the exact pattern from GetBacktestingResult
|
||||||
|
var finalPnl = tradingBot.GetProfitAndLoss();
|
||||||
|
var winRate = tradingBot.GetWinRate();
|
||||||
|
var stats = TradingHelpers.GetStatistics(tradingBot.WalletBalances);
|
||||||
|
var growthPercentage =
|
||||||
|
TradingHelpers.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, finalPnl);
|
||||||
|
var hodlPercentage = TradingHelpers.GetHodlPercentage(candles[0], candles.Last());
|
||||||
|
|
||||||
|
var fees = tradingBot.GetTotalFees();
|
||||||
|
var scoringParams = new BacktestScoringParams(
|
||||||
|
sharpeRatio: (double)stats.SharpeRatio,
|
||||||
|
growthPercentage: (double)growthPercentage,
|
||||||
|
hodlPercentage: (double)hodlPercentage,
|
||||||
|
winRate: winRate,
|
||||||
|
totalPnL: (double)finalPnl,
|
||||||
|
fees: (double)fees,
|
||||||
|
tradeCount: tradingBot.Positions.Count,
|
||||||
|
maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime,
|
||||||
|
maxDrawdown: stats.MaxDrawdown,
|
||||||
|
initialBalance: tradingBot.WalletBalances.FirstOrDefault().Value,
|
||||||
|
tradingBalance: config.BotTradingBalance,
|
||||||
|
startDate: candles[0].Date,
|
||||||
|
endDate: candles.Last().Date,
|
||||||
|
timeframe: config.Timeframe,
|
||||||
|
moneyManagement: config.MoneyManagement
|
||||||
|
);
|
||||||
|
|
||||||
|
var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
|
||||||
|
|
||||||
|
// Generate requestId if not provided
|
||||||
|
var finalRequestId = requestId ?? Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// Create backtest result with conditional candles and indicators values
|
||||||
|
var result = new Backtest(config, tradingBot.Positions, tradingBot.Signals.ToList(),
|
||||||
|
withCandles ? candles : new List<Candle>())
|
||||||
|
{
|
||||||
|
FinalPnl = finalPnl,
|
||||||
|
WinRate = winRate,
|
||||||
|
GrowthPercentage = growthPercentage,
|
||||||
|
HodlPercentage = hodlPercentage,
|
||||||
|
Fees = fees,
|
||||||
|
WalletBalances = tradingBot.WalletBalances.ToList(),
|
||||||
|
Statistics = stats,
|
||||||
|
IndicatorsValues = withCandles
|
||||||
|
? AggregateValues(indicatorsValues, tradingBot.IndicatorsValues)
|
||||||
|
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
||||||
|
Score = scoringResult.Score,
|
||||||
|
ScoreMessage = scoringResult.GenerateSummaryMessage(),
|
||||||
|
Id = Guid.NewGuid().ToString(),
|
||||||
|
RequestId = finalRequestId,
|
||||||
|
Metadata = metadata,
|
||||||
|
StartDate = candles.FirstOrDefault()!.OpenTime,
|
||||||
|
EndDate = candles.LastOrDefault()!.OpenTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (save && user != null)
|
||||||
|
{
|
||||||
|
_backtestRepository.InsertBacktestForUser(user, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notification if backtest meets criteria
|
||||||
|
await SendBacktestNotificationIfCriteriaMet(result);
|
||||||
|
|
||||||
|
// Clean up the trading bot instance
|
||||||
|
tradingBot.Stop();
|
||||||
|
|
||||||
|
// Convert Backtest to LightBacktest for safe Orleans serialization
|
||||||
|
return ConvertToLightBacktest(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a Backtest to LightBacktest for safe Orleans serialization
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backtest">The full backtest to convert</param>
|
||||||
|
/// <returns>A lightweight backtest suitable for Orleans serialization</returns>
|
||||||
|
private LightBacktest ConvertToLightBacktest(Backtest backtest)
|
||||||
|
{
|
||||||
|
return new LightBacktest
|
||||||
|
{
|
||||||
|
Id = backtest.Id,
|
||||||
|
Config = backtest.Config,
|
||||||
|
FinalPnl = backtest.FinalPnl,
|
||||||
|
WinRate = backtest.WinRate,
|
||||||
|
GrowthPercentage = backtest.GrowthPercentage,
|
||||||
|
HodlPercentage = backtest.HodlPercentage,
|
||||||
|
StartDate = backtest.StartDate,
|
||||||
|
EndDate = backtest.EndDate,
|
||||||
|
MaxDrawdown = backtest.Statistics?.MaxDrawdown,
|
||||||
|
Fees = backtest.Fees,
|
||||||
|
SharpeRatio = (double?)backtest.Statistics?.SharpeRatio,
|
||||||
|
Score = backtest.Score,
|
||||||
|
ScoreMessage = backtest.ScoreMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a TradingBotBase instance using composition for backtesting
|
||||||
|
/// </summary>
|
||||||
|
private async Task<TradingBotBase> CreateTradingBotInstance(TradingBotConfig config, User user = null)
|
||||||
|
{
|
||||||
|
// Validate configuration for backtesting
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot configuration is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.IsForBacktest)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("BacktestTradingBotGrain can only be used for backtesting");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the trading bot instance
|
||||||
|
var logger = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<TradingBotBase>>();
|
||||||
|
var tradingBot = new TradingBotBase(logger, _scopeFactory, config);
|
||||||
|
|
||||||
|
// Set the user if available
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
tradingBot.User = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tradingBot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends notification if backtest meets criteria (following Backtester.cs pattern)
|
||||||
|
/// </summary>
|
||||||
|
private async Task SendBacktestNotificationIfCriteriaMet(Backtest backtest)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (backtest.Score > 60)
|
||||||
|
{
|
||||||
|
// Note: In a real implementation, you would inject IMessengerService
|
||||||
|
// For now, we'll just log the notification
|
||||||
|
_logger.LogInformation("Backtest {BacktestId} scored {Score} - notification criteria met",
|
||||||
|
backtest.Id, backtest.Score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send backtest notification for backtest {Id}", backtest.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggregates indicator values (following Backtester.cs pattern)
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<IndicatorType, IndicatorsResultBase> AggregateValues(
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues,
|
||||||
|
Dictionary<IndicatorType, IndicatorsResultBase> botStrategiesValues)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||||
|
foreach (var indicator in indicatorsValues)
|
||||||
|
{
|
||||||
|
result[indicator.Key] = indicator.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets indicators values (following Backtester.cs pattern)
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<IndicatorType, IndicatorsResultBase> GetIndicatorsValues(List<Indicator> indicators,
|
||||||
|
List<Candle> candles)
|
||||||
|
{
|
||||||
|
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||||
|
var fixedCandles = new FixedSizeQueue<Candle>(10000);
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
fixedCandles.Enqueue(candle);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var indicator in indicators)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var s = ScenarioHelpers.BuildIndicator(indicator, 10000);
|
||||||
|
s.Candles = fixedCandles;
|
||||||
|
indicatorsValues[indicator.Type] = s.GetIndicatorValues();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error building indicator {IndicatorType}", indicator.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indicatorsValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<BacktestProgress> GetBacktestProgressAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<BotStatus> GetStatusAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TradingBotConfig> GetConfigurationAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Position> OpenPositionManuallyAsync(TradeDirection direction)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ToggleIsForWatchOnlyAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TradingBotResponse> GetBotDataAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task LoadBackupAsync(BotBackup backup)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SaveBackupAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<decimal> GetProfitAndLossAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> GetWinRateAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<long> GetExecutionCountAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DateTime> GetStartupTimeAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DateTime> GetCreateDateAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
496
src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
Normal file
496
src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
|
using Managing.Application.Abstractions.Models;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Bots.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain for live trading bot operations.
|
||||||
|
/// Uses composition with TradingBotBase to maintain separation of concerns.
|
||||||
|
/// This grain handles live trading scenarios with real-time market data and execution.
|
||||||
|
/// </summary>
|
||||||
|
public class LiveTradingBotGrain : Grain<TradingBotGrainState>, ITradingBotGrain
|
||||||
|
{
|
||||||
|
private readonly ILogger<LiveTradingBotGrain> _logger;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private TradingBotBase? _tradingBot;
|
||||||
|
private IDisposable? _timer;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
public LiveTradingBotGrain(
|
||||||
|
ILogger<LiveTradingBotGrain> logger,
|
||||||
|
IServiceScopeFactory scopeFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await base.OnActivateAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} activated", this.GetPrimaryKey());
|
||||||
|
|
||||||
|
// Initialize the grain state if not already done
|
||||||
|
if (!State.IsInitialized)
|
||||||
|
{
|
||||||
|
State.Identifier = this.GetPrimaryKey().ToString();
|
||||||
|
State.CreateDate = DateTime.UtcNow;
|
||||||
|
State.Status = BotStatus.Down;
|
||||||
|
State.IsInitialized = true;
|
||||||
|
await WriteStateAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} deactivating. Reason: {Reason}",
|
||||||
|
this.GetPrimaryKey(), reason.Description);
|
||||||
|
|
||||||
|
// Stop the timer and trading bot
|
||||||
|
await StopAsync();
|
||||||
|
|
||||||
|
await base.OnDeactivateAsync(reason, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (State.Status == BotStatus.Up)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Bot {GrainId} is already running", this.GetPrimaryKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (State.Config == null || string.IsNullOrEmpty(State.Config.Name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot configuration is not properly initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure this is not a backtest configuration
|
||||||
|
if (State.Config.IsForBacktest)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("LiveTradingBotGrain cannot be used for backtesting");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the TradingBotBase instance using composition
|
||||||
|
_tradingBot = await CreateTradingBotInstance();
|
||||||
|
|
||||||
|
// Load backup if available
|
||||||
|
if (State.User != null)
|
||||||
|
{
|
||||||
|
await LoadBackupFromState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the trading bot
|
||||||
|
_tradingBot.Start();
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
State.Status = BotStatus.Up;
|
||||||
|
State.StartupTime = DateTime.UtcNow;
|
||||||
|
await WriteStateAsync();
|
||||||
|
|
||||||
|
// Start Orleans timer for periodic execution
|
||||||
|
StartTimer();
|
||||||
|
|
||||||
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} started successfully", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to start LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
State.Status = BotStatus.Down;
|
||||||
|
await WriteStateAsync();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Stop the timer
|
||||||
|
_timer?.Dispose();
|
||||||
|
_timer = null;
|
||||||
|
|
||||||
|
// Stop the trading bot
|
||||||
|
if (_tradingBot != null)
|
||||||
|
{
|
||||||
|
_tradingBot.Stop();
|
||||||
|
|
||||||
|
// Save backup before stopping
|
||||||
|
await SaveBackupToState();
|
||||||
|
|
||||||
|
_tradingBot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
State.Status = BotStatus.Down;
|
||||||
|
await WriteStateAsync();
|
||||||
|
|
||||||
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} stopped successfully", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to stop LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<BotStatus> GetStatusAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(State.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TradingBotConfig> GetConfigurationAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(State.Config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateConfigurationAsync(TradingBotConfig newConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure this is not a backtest configuration
|
||||||
|
if (newConfig.IsForBacktest)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("LiveTradingBotGrain cannot be used for backtesting");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the configuration in the trading bot
|
||||||
|
var success = await _tradingBot.UpdateConfiguration(newConfig);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
// Update the state
|
||||||
|
State.Config = newConfig;
|
||||||
|
await WriteStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to update configuration for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Position> OpenPositionManuallyAsync(TradeDirection direction)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _tradingBot.OpenPositionManually(direction);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to open manual position for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ToggleIsForWatchOnlyAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _tradingBot.ToggleIsForWatchOnly();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to toggle watch-only mode for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TradingBotResponse> GetBotDataAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TradingBotResponse
|
||||||
|
{
|
||||||
|
Identifier = State.Identifier,
|
||||||
|
Name = State.Name,
|
||||||
|
Status = State.Status,
|
||||||
|
Config = State.Config,
|
||||||
|
Positions = _tradingBot.Positions,
|
||||||
|
Signals = _tradingBot.Signals.ToList(),
|
||||||
|
WalletBalances = _tradingBot.WalletBalances,
|
||||||
|
ProfitAndLoss = _tradingBot.GetProfitAndLoss(),
|
||||||
|
WinRate = _tradingBot.GetWinRate(),
|
||||||
|
ExecutionCount = _tradingBot.ExecutionCount,
|
||||||
|
StartupTime = State.StartupTime,
|
||||||
|
CreateDate = State.CreateDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get bot data for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadBackupAsync(BotBackup backup)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
_tradingBot.LoadBackup(backup);
|
||||||
|
|
||||||
|
// Update state from backup
|
||||||
|
State.User = backup.User;
|
||||||
|
State.Identifier = backup.Identifier;
|
||||||
|
State.Status = backup.LastStatus;
|
||||||
|
State.CreateDate = backup.Data.CreateDate;
|
||||||
|
State.StartupTime = backup.Data.StartupTime;
|
||||||
|
await WriteStateAsync();
|
||||||
|
|
||||||
|
_logger.LogInformation("Backup loaded successfully for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to load backup for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveBackupAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _tradingBot.SaveBackup();
|
||||||
|
await SaveBackupToState();
|
||||||
|
|
||||||
|
_logger.LogInformation("Backup saved successfully for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to save backup for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetProfitAndLossAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _tradingBot.GetProfitAndLoss();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get P&L for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetWinRateAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _tradingBot.GetWinRate();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get win rate for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<long> GetExecutionCountAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(State.ExecutionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DateTime> GetStartupTimeAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(State.StartupTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DateTime> GetCreateDateAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(State.CreateDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a TradingBotBase instance using composition
|
||||||
|
/// </summary>
|
||||||
|
private async Task<TradingBotBase> CreateTradingBotInstance()
|
||||||
|
{
|
||||||
|
// Validate configuration for live trading
|
||||||
|
if (State.Config == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bot configuration is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (State.Config.IsForBacktest)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("LiveTradingBotGrain cannot be used for backtesting");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(State.Config.AccountName))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Account name is required for live trading");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the trading bot instance
|
||||||
|
var logger = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<TradingBotBase>>();
|
||||||
|
var tradingBot = new TradingBotBase(logger, _scopeFactory, State.Config);
|
||||||
|
|
||||||
|
// Set the user if available
|
||||||
|
if (State.User != null)
|
||||||
|
{
|
||||||
|
tradingBot.User = State.User;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tradingBot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the Orleans timer for periodic bot execution
|
||||||
|
/// </summary>
|
||||||
|
private void StartTimer()
|
||||||
|
{
|
||||||
|
if (_tradingBot == null) return;
|
||||||
|
|
||||||
|
var interval = _tradingBot.Interval;
|
||||||
|
_timer = RegisterTimer(
|
||||||
|
async _ => await ExecuteBotCycle(),
|
||||||
|
null,
|
||||||
|
TimeSpan.FromMilliseconds(interval),
|
||||||
|
TimeSpan.FromMilliseconds(interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes one cycle of the trading bot
|
||||||
|
/// </summary>
|
||||||
|
private async Task ExecuteBotCycle()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tradingBot == null || State.Status != BotStatus.Up || _isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the bot's Run method
|
||||||
|
await _tradingBot.Run();
|
||||||
|
|
||||||
|
// Update execution count
|
||||||
|
State.ExecutionCount++;
|
||||||
|
|
||||||
|
await SaveBackupToState();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Gracefully handle disposed service provider during shutdown
|
||||||
|
_logger.LogInformation("Service provider disposed during shutdown for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during bot execution cycle for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current bot state to Orleans state storage
|
||||||
|
/// </summary>
|
||||||
|
private async Task SaveBackupToState()
|
||||||
|
{
|
||||||
|
if (_tradingBot == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sync state from TradingBotBase
|
||||||
|
State.Config = _tradingBot.Config;
|
||||||
|
State.Signals = _tradingBot.Signals;
|
||||||
|
State.Positions = _tradingBot.Positions;
|
||||||
|
State.WalletBalances = _tradingBot.WalletBalances;
|
||||||
|
State.PreloadSince = _tradingBot.PreloadSince;
|
||||||
|
State.PreloadedCandlesCount = _tradingBot.PreloadedCandlesCount;
|
||||||
|
State.Interval = _tradingBot.Interval;
|
||||||
|
State.MaxSignals = _tradingBot._maxSignals;
|
||||||
|
State.LastBackupTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await WriteStateAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to save state for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads bot state from Orleans state storage
|
||||||
|
/// </summary>
|
||||||
|
private async Task LoadBackupFromState()
|
||||||
|
{
|
||||||
|
if (_tradingBot == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sync state to TradingBotBase
|
||||||
|
_tradingBot.Signals = State.Signals;
|
||||||
|
_tradingBot.Positions = State.Positions;
|
||||||
|
_tradingBot.WalletBalances = State.WalletBalances;
|
||||||
|
_tradingBot.PreloadSince = State.PreloadSince;
|
||||||
|
_tradingBot.PreloadedCandlesCount = State.PreloadedCandlesCount;
|
||||||
|
_tradingBot.Config = State.Config;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to load state for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_timer?.Dispose();
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,12 @@ namespace Managing.Application.Bots
|
|||||||
{
|
{
|
||||||
public class SimpleBot : Bot
|
public class SimpleBot : Bot
|
||||||
{
|
{
|
||||||
public readonly ILogger<TradingBot> Logger;
|
public readonly ILogger<TradingBotBase> Logger;
|
||||||
private readonly IBotService _botService;
|
private readonly IBotService _botService;
|
||||||
private readonly IBackupBotService _backupBotService;
|
private readonly IBackupBotService _backupBotService;
|
||||||
private Workflow _workflow;
|
private Workflow _workflow;
|
||||||
|
|
||||||
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService,
|
public SimpleBot(string name, ILogger<TradingBotBase> logger, Workflow workflow, IBotService botService,
|
||||||
IBackupBotService backupBotService) :
|
IBackupBotService backupBotService) :
|
||||||
base(name)
|
base(name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ using static Managing.Common.Enums;
|
|||||||
|
|
||||||
namespace Managing.Application.Bots;
|
namespace Managing.Application.Bots;
|
||||||
|
|
||||||
public class TradingBot : Bot, ITradingBot
|
public class TradingBotBase : Bot, ITradingBot
|
||||||
{
|
{
|
||||||
public readonly ILogger<TradingBot> Logger;
|
public readonly ILogger<TradingBotBase> Logger;
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
public TradingBotConfig Config { get; set; }
|
public TradingBotConfig Config { get; set; }
|
||||||
@@ -41,8 +41,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
public int _maxSignals = 10; // Maximum number of signals to keep in memory
|
public int _maxSignals = 10; // Maximum number of signals to keep in memory
|
||||||
|
|
||||||
public TradingBot(
|
public TradingBotBase(
|
||||||
ILogger<TradingBot> logger,
|
ILogger<TradingBotBase> logger,
|
||||||
IServiceScopeFactory scopeFactory,
|
IServiceScopeFactory scopeFactory,
|
||||||
TradingBotConfig config
|
TradingBotConfig config
|
||||||
)
|
)
|
||||||
@@ -71,7 +71,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// Load indicators if scenario is provided in config
|
// Load indicators if scenario is provided in config
|
||||||
if (Config.Scenario != null)
|
if (Config.Scenario != null)
|
||||||
{
|
{
|
||||||
LoadIndicators(Config.Scenario);
|
// Convert LightScenario to full Scenario for indicator loading
|
||||||
|
var fullScenario = Config.Scenario.ToScenario();
|
||||||
|
LoadIndicators(fullScenario);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -151,8 +153,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadAccount()
|
public async Task LoadAccount()
|
||||||
@@ -185,8 +185,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Store the scenario in config and load indicators
|
// Convert full Scenario to LightScenario for storage and load indicators
|
||||||
Config.Scenario = scenario;
|
Config.Scenario = LightScenario.FromScenario(scenario);
|
||||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||||
|
|
||||||
Logger.LogInformation($"Loaded scenario '{scenario.Name}' with {Indicators.Count} indicators");
|
Logger.LogInformation($"Loaded scenario '{scenario.Name}' with {Indicators.Count} indicators");
|
||||||
@@ -1594,6 +1594,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
public override async Task SaveBackup()
|
public override async Task SaveBackup()
|
||||||
{
|
{
|
||||||
|
if (Config.IsForBacktest)
|
||||||
|
return;
|
||||||
|
|
||||||
var data = new TradingBotBackup
|
var data = new TradingBotBackup
|
||||||
{
|
{
|
||||||
Config = Config,
|
Config = Config,
|
||||||
@@ -1908,7 +1911,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
{
|
{
|
||||||
if (newConfig.Scenario != null)
|
if (newConfig.Scenario != null)
|
||||||
{
|
{
|
||||||
LoadScenario(newConfig.Scenario);
|
// Convert LightScenario to full Scenario for loading
|
||||||
|
var fullScenario = newConfig.Scenario.ToScenario();
|
||||||
|
LoadScenario(fullScenario);
|
||||||
|
|
||||||
// Compare indicators after scenario change
|
// Compare indicators after scenario change
|
||||||
var newIndicators = Indicators?.ToList() ?? new List<IIndicator>();
|
var newIndicators = Indicators?.ToList() ?? new List<IIndicator>();
|
||||||
@@ -2068,14 +2073,14 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isInCooldown = positionClosingDate >= cooldownCandle.Date;
|
var isInCooldown = positionClosingDate >= cooldownCandle.Date;
|
||||||
|
|
||||||
if (isInCooldown)
|
if (isInCooldown)
|
||||||
{
|
{
|
||||||
var intervalMilliseconds = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
|
var intervalMilliseconds = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
|
||||||
var intervalMinutes = intervalMilliseconds / (1000.0 * 60.0); // Convert milliseconds to minutes
|
var intervalMinutes = intervalMilliseconds / (1000.0 * 60.0); // Convert milliseconds to minutes
|
||||||
var cooldownEndTime = cooldownCandle.Date.AddMinutes(intervalMinutes * Config.CooldownPeriod);
|
var cooldownEndTime = cooldownCandle.Date.AddMinutes(intervalMinutes * Config.CooldownPeriod);
|
||||||
var remainingTime = cooldownEndTime - DateTime.UtcNow;
|
var remainingTime = cooldownEndTime - DateTime.UtcNow;
|
||||||
|
|
||||||
Logger.LogWarning(
|
Logger.LogWarning(
|
||||||
$"⏳ **Cooldown Period Active**\n" +
|
$"⏳ **Cooldown Period Active**\n" +
|
||||||
$"Cannot open new positions\n" +
|
$"Cannot open new positions\n" +
|
||||||
117
src/Managing.Application/Bots/TradingBotGrainState.cs
Normal file
117
src/Managing.Application/Bots/TradingBotGrainState.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Bots;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain state for TradingBot.
|
||||||
|
/// This class represents the persistent state of a trading bot grain.
|
||||||
|
/// All properties must be serializable for Orleans state management.
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class TradingBotGrainState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The trading bot configuration
|
||||||
|
/// </summary>
|
||||||
|
[Id(0)]
|
||||||
|
public TradingBotConfig Config { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of trading signals generated by the bot
|
||||||
|
/// </summary>
|
||||||
|
[Id(1)]
|
||||||
|
public HashSet<LightSignal> Signals { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of trading positions opened by the bot
|
||||||
|
/// </summary>
|
||||||
|
[Id(2)]
|
||||||
|
public List<Position> Positions { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Historical wallet balances tracked over time
|
||||||
|
/// </summary>
|
||||||
|
[Id(3)]
|
||||||
|
public Dictionary<DateTime, decimal> WalletBalances { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current status of the bot (Running, Stopped, etc.)
|
||||||
|
/// </summary>
|
||||||
|
[Id(4)]
|
||||||
|
public BotStatus Status { get; set; } = BotStatus.Down;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the bot was started
|
||||||
|
/// </summary>
|
||||||
|
[Id(5)]
|
||||||
|
public DateTime StartupTime { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the bot was created
|
||||||
|
/// </summary>
|
||||||
|
[Id(6)]
|
||||||
|
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The user who owns this bot
|
||||||
|
/// </summary>
|
||||||
|
[Id(7)]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot execution counter
|
||||||
|
/// </summary>
|
||||||
|
[Id(8)]
|
||||||
|
public long ExecutionCount { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot identifier/name
|
||||||
|
/// </summary>
|
||||||
|
[Id(9)]
|
||||||
|
public string Identifier { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot display name
|
||||||
|
/// </summary>
|
||||||
|
[Id(10)]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Preload start date for candles
|
||||||
|
/// </summary>
|
||||||
|
[Id(11)]
|
||||||
|
public DateTime PreloadSince { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of preloaded candles
|
||||||
|
/// </summary>
|
||||||
|
[Id(12)]
|
||||||
|
public int PreloadedCandlesCount { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer interval for bot execution
|
||||||
|
/// </summary>
|
||||||
|
[Id(13)]
|
||||||
|
public int Interval { get; set; } = 60000; // Default 1 minute
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of signals to keep in memory
|
||||||
|
/// </summary>
|
||||||
|
[Id(14)]
|
||||||
|
public int MaxSignals { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the bot has been initialized
|
||||||
|
/// </summary>
|
||||||
|
[Id(15)]
|
||||||
|
public bool IsInitialized { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last time the bot state was persisted
|
||||||
|
/// </summary>
|
||||||
|
[Id(16)]
|
||||||
|
public DateTime LastBackupTime { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -776,7 +776,7 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
UseForPositionSizing = false,
|
UseForPositionSizing = false,
|
||||||
UseForSignalFiltering = false,
|
UseForSignalFiltering = false,
|
||||||
UseForDynamicStopLoss = false,
|
UseForDynamicStopLoss = false,
|
||||||
Scenario = scenario,
|
Scenario = LightScenario.FromScenario(scenario),
|
||||||
MoneyManagement = mm,
|
MoneyManagement = mm,
|
||||||
RiskManagement = new RiskManagement
|
RiskManagement = new RiskManagement
|
||||||
{
|
{
|
||||||
@@ -915,7 +915,7 @@ public class TradingBotFitness : IFitness
|
|||||||
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
|
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
|
||||||
|
|
||||||
// Run backtest using scoped service to avoid DbContext concurrency issues
|
// Run backtest using scoped service to avoid DbContext concurrency issues
|
||||||
var backtest = ServiceScopeHelpers.WithScopedService<IBacktester, Backtest>(
|
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>(
|
||||||
_serviceScopeFactory,
|
_serviceScopeFactory,
|
||||||
backtester => backtester.RunTradingBotBacktest(
|
backtester => backtester.RunTradingBotBacktest(
|
||||||
config,
|
config,
|
||||||
@@ -933,7 +933,7 @@ public class TradingBotFitness : IFitness
|
|||||||
).Result;
|
).Result;
|
||||||
|
|
||||||
// Calculate multi-objective fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
var fitness = CalculateFitness(backtest, config);
|
var fitness = CalculateFitness(lightBacktest, config);
|
||||||
|
|
||||||
return fitness;
|
return fitness;
|
||||||
}
|
}
|
||||||
@@ -945,13 +945,13 @@ public class TradingBotFitness : IFitness
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double CalculateFitness(Backtest backtest, TradingBotConfig config)
|
private double CalculateFitness(LightBacktest lightBacktest, TradingBotConfig config)
|
||||||
{
|
{
|
||||||
if (backtest == null || backtest.Statistics == null)
|
if (lightBacktest == null)
|
||||||
return 0.1;
|
return 0.1;
|
||||||
|
|
||||||
// Calculate base fitness from backtest score
|
// Calculate base fitness from backtest score
|
||||||
var baseFitness = backtest.Score;
|
var baseFitness = lightBacktest.Score;
|
||||||
|
|
||||||
// Return base fitness (no penalty for now)
|
// Return base fitness (no penalty for now)
|
||||||
return baseFitness;
|
return baseFitness;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Managing.Application.Abstractions.Repositories;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Bots;
|
using Managing.Application.Bots;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -18,20 +19,21 @@ namespace Managing.Application.ManageBot
|
|||||||
private readonly IExchangeService _exchangeService;
|
private readonly IExchangeService _exchangeService;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
private readonly ILogger<TradingBotBase> _tradingBotLogger;
|
||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IBackupBotService _backupBotService;
|
private readonly IBackupBotService _backupBotService;
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly IGrainFactory _grainFactory;
|
||||||
|
|
||||||
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
||||||
new ConcurrentDictionary<string, BotTaskWrapper>();
|
new ConcurrentDictionary<string, BotTaskWrapper>();
|
||||||
|
|
||||||
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
||||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
|
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBotBase> tradingBotLogger,
|
||||||
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
|
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
|
||||||
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory)
|
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory, IGrainFactory grainFactory)
|
||||||
{
|
{
|
||||||
_botRepository = botRepository;
|
_botRepository = botRepository;
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
@@ -43,26 +45,26 @@ namespace Managing.Application.ManageBot
|
|||||||
_userService = userService;
|
_userService = userService;
|
||||||
_backupBotService = backupBotService;
|
_backupBotService = backupBotService;
|
||||||
_scopeFactory = scopeFactory;
|
_scopeFactory = scopeFactory;
|
||||||
|
_grainFactory = grainFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BotTaskWrapper
|
public class BotTaskWrapper
|
||||||
{
|
{
|
||||||
public Task Task { get; private set; }
|
public Task Task { get; private set; }
|
||||||
public Type BotType { get; private set; }
|
public Type BotType { get; private set; }
|
||||||
public object BotInstance { get; private set; } // Add this line
|
public object BotInstance { get; private set; }
|
||||||
|
|
||||||
public BotTaskWrapper(Task task, Type botType, object botInstance) // Update constructor
|
public BotTaskWrapper(Task task, Type botType, object botInstance)
|
||||||
{
|
{
|
||||||
Task = task;
|
Task = task;
|
||||||
BotType = botType;
|
BotType = botType;
|
||||||
BotInstance = botInstance; // Set the bot instance
|
BotInstance = botInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSimpleBotToCache(IBot bot)
|
public void AddSimpleBotToCache(IBot bot)
|
||||||
{
|
{
|
||||||
var botTask =
|
var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot);
|
||||||
new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance
|
|
||||||
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,24 +74,34 @@ namespace Managing.Application.ManageBot
|
|||||||
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task InitBot(ITradingBot bot, BotBackup backupBot)
|
private async Task InitBot(ITradingBot bot, BotBackup backupBot)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUser(backupBot.User.Name);
|
try
|
||||||
bot.User = user;
|
{
|
||||||
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
|
var user = await _userService.GetUser(backupBot.User.Name);
|
||||||
bot.LoadBackup(backupBot);
|
bot.User = user;
|
||||||
|
|
||||||
|
// Load backup data into the bot
|
||||||
|
bot.LoadBackup(backupBot);
|
||||||
|
|
||||||
// Only start the bot if the backup status is Up
|
// Only start the bot if the backup status is Up
|
||||||
if (backupBot.LastStatus == BotStatus.Up)
|
if (backupBot.LastStatus == BotStatus.Up)
|
||||||
{
|
{
|
||||||
// Start the bot asynchronously without waiting for completion
|
// Start the bot asynchronously without waiting for completion
|
||||||
_ = Task.Run(() => bot.Start());
|
_ = Task.Run(() => bot.Start());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Keep the bot in Down status if it was originally Down
|
||||||
|
bot.Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Keep the bot in Down status if it was originally Down
|
_tradingBotLogger.LogError(ex, "Error initializing bot {Identifier} from backup", backupBot.Identifier);
|
||||||
|
// Ensure the bot is stopped if initialization fails
|
||||||
bot.Stop();
|
bot.Stop();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +149,7 @@ namespace Managing.Application.ManageBot
|
|||||||
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
scalpingConfig.Scenario = scenario;
|
scalpingConfig.Scenario = LightScenario.FromScenario(scenario);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -155,6 +167,10 @@ namespace Managing.Application.ManageBot
|
|||||||
// Ensure critical properties are set correctly for restored bots
|
// Ensure critical properties are set correctly for restored bots
|
||||||
scalpingConfig.IsForBacktest = false;
|
scalpingConfig.IsForBacktest = false;
|
||||||
|
|
||||||
|
// IMPORTANT: Save the backup to database BEFORE creating the Orleans grain
|
||||||
|
// This ensures the backup exists when the grain tries to serialize it
|
||||||
|
await SaveOrUpdateBotBackup(backupBot.User, backupBot.Identifier, backupBot.LastStatus, backupBot.Data);
|
||||||
|
|
||||||
bot = await CreateTradingBot(scalpingConfig);
|
bot = await CreateTradingBot(scalpingConfig);
|
||||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||||
|
|
||||||
@@ -206,7 +222,7 @@ namespace Managing.Application.ManageBot
|
|||||||
if (botWrapper.BotInstance is IBot bot)
|
if (botWrapper.BotInstance is IBot bot)
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
|
bot.Stop());
|
||||||
|
|
||||||
var stopMessage = $"🛑 **Bot Stopped**\n\n" +
|
var stopMessage = $"🛑 **Bot Stopped**\n\n" +
|
||||||
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
||||||
@@ -231,7 +247,7 @@ namespace Managing.Application.ManageBot
|
|||||||
if (botWrapper.BotInstance is IBot bot)
|
if (botWrapper.BotInstance is IBot bot)
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
|
bot.Stop());
|
||||||
|
|
||||||
var deleteMessage = $"🗑️ **Bot Deleted**\n\n" +
|
var deleteMessage = $"🗑️ **Bot Deleted**\n\n" +
|
||||||
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
||||||
@@ -306,7 +322,7 @@ namespace Managing.Application.ManageBot
|
|||||||
public async Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig)
|
public async Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig)
|
||||||
{
|
{
|
||||||
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
||||||
botTaskWrapper.BotInstance is TradingBot tradingBot)
|
botTaskWrapper.BotInstance is TradingBotBase tradingBot)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||||
@@ -314,7 +330,7 @@ namespace Managing.Application.ManageBot
|
|||||||
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
newConfig.Scenario = scenario;
|
newConfig.Scenario = LightScenario.FromScenario(scenario);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -370,7 +386,6 @@ namespace Managing.Application.ManageBot
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
@@ -379,7 +394,7 @@ namespace Managing.Application.ManageBot
|
|||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = LightScenario.FromScenario(scenario);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -392,7 +407,15 @@ namespace Managing.Application.ManageBot
|
|||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
// For now, use TradingBot for both live trading and backtesting
|
||||||
|
// TODO: Implement Orleans grain for live trading when ready
|
||||||
|
if (!config.IsForBacktest)
|
||||||
|
{
|
||||||
|
// Ensure critical properties are set correctly for live trading
|
||||||
|
config.IsForBacktest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TradingBotBase(_tradingBotLogger, _scopeFactory, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
|
||||||
@@ -403,7 +426,7 @@ namespace Managing.Application.ManageBot
|
|||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = LightScenario.FromScenario(scenario);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -417,109 +440,7 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.IsForBacktest = true;
|
config.IsForBacktest = true;
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
return new TradingBotBase(_tradingBotLogger, _scopeFactory, config);
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
|
|
||||||
{
|
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
|
||||||
{
|
|
||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
|
||||||
if (scenario != null)
|
|
||||||
{
|
|
||||||
config.Scenario = scenario;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.Scenario == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
config.FlipPosition = false;
|
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
|
|
||||||
{
|
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
|
||||||
{
|
|
||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
|
||||||
if (scenario != null)
|
|
||||||
{
|
|
||||||
config.Scenario = scenario;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.Scenario == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
config.IsForBacktest = true;
|
|
||||||
config.FlipPosition = false;
|
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
|
|
||||||
{
|
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
|
||||||
{
|
|
||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
|
||||||
if (scenario != null)
|
|
||||||
{
|
|
||||||
config.Scenario = scenario;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.Scenario == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
config.FlipPosition = true;
|
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
|
|
||||||
{
|
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
|
||||||
{
|
|
||||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
|
||||||
if (scenario != null)
|
|
||||||
{
|
|
||||||
config.Scenario = scenario;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.Scenario == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
config.IsForBacktest = true;
|
|
||||||
config.FlipPosition = true;
|
|
||||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
|
|
||||||
// Try to get the active bot multiple times to ensure it's properly started
|
// Try to get the active bot multiple times to ensure it's properly started
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
const int maxAttempts = 5;
|
const int maxAttempts = 2;
|
||||||
|
|
||||||
while (attempts < maxAttempts)
|
while (attempts < maxAttempts)
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,8 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
if (backupBot.LastStatus == BotStatus.Down)
|
if (backupBot.LastStatus == BotStatus.Down)
|
||||||
{
|
{
|
||||||
result[activeBot.Identifier] = BotStatus.Down;
|
result[activeBot.Identifier] = BotStatus.Down;
|
||||||
_logger.LogInformation("Backup bot {Identifier} loaded but kept in Down status as it was originally Down.",
|
_logger.LogInformation(
|
||||||
|
"Backup bot {Identifier} loaded but kept in Down status as it was originally Down.",
|
||||||
backupBot.Identifier);
|
backupBot.Identifier);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -68,6 +69,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
_logger.LogInformation("Backup bot {Identifier} started successfully.",
|
_logger.LogInformation("Backup bot {Identifier} started successfully.",
|
||||||
backupBot.Identifier);
|
backupBot.Identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,29 +7,31 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="MoneyManagements\Abstractions\**"/>
|
<Compile Remove="MoneyManagements\Abstractions\**" />
|
||||||
<EmbeddedResource Remove="MoneyManagements\Abstractions\**"/>
|
<EmbeddedResource Remove="MoneyManagements\Abstractions\**" />
|
||||||
<None Remove="MoneyManagements\Abstractions\**"/>
|
<None Remove="MoneyManagements\Abstractions\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentValidation" Version="11.9.1"/>
|
<PackageReference Include="FluentValidation" Version="11.9.1" />
|
||||||
<PackageReference Include="GeneticSharp" Version="3.1.4"/>
|
<PackageReference Include="GeneticSharp" Version="3.1.4" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0"/>
|
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0"/>
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0"/>
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||||
<PackageReference Include="Polly" Version="8.4.0"/>
|
<PackageReference Include="Microsoft.Orleans.Client" Version="9.2.1" />
|
||||||
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
|
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="9.2.1" />
|
||||||
|
<PackageReference Include="Polly" Version="8.4.0" />
|
||||||
|
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
|
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
|
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ namespace Managing.Application.Scenarios
|
|||||||
return scenarios.Where(s => s.User?.Name == user.Name);
|
return scenarios.Where(s => s.User?.Name == user.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
|
public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies,
|
||||||
|
int? loopbackPeriod = 1)
|
||||||
{
|
{
|
||||||
var scenario = new Scenario(name, loopbackPeriod ?? 1)
|
var scenario = new Scenario(name, loopbackPeriod ?? 1)
|
||||||
{
|
{
|
||||||
@@ -193,6 +194,7 @@ namespace Managing.Application.Scenarios
|
|||||||
{
|
{
|
||||||
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -211,6 +213,7 @@ namespace Managing.Application.Scenarios
|
|||||||
{
|
{
|
||||||
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -226,7 +229,8 @@ namespace Managing.Application.Scenarios
|
|||||||
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
|
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Indicator> CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null,
|
public async Task<Indicator> CreateIndicatorForUser(User user, IndicatorType type, string name,
|
||||||
|
int? period = null,
|
||||||
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
|
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
|
||||||
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
|
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
|
||||||
int? cyclePeriods = null)
|
int? cyclePeriods = null)
|
||||||
@@ -253,6 +257,7 @@ namespace Managing.Application.Scenarios
|
|||||||
{
|
{
|
||||||
await _tradingService.DeleteStrategyAsync(strategy.Name);
|
await _tradingService.DeleteStrategyAsync(strategy.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -262,7 +267,8 @@ namespace Managing.Application.Scenarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
|
public async Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies,
|
||||||
|
int? loopbackPeriod)
|
||||||
{
|
{
|
||||||
var scenario = await _tradingService.GetScenarioByNameAsync(name);
|
var scenario = await _tradingService.GetScenarioByNameAsync(name);
|
||||||
if (scenario == null || scenario.User?.Name != user.Name)
|
if (scenario == null || scenario.User?.Name != user.Name)
|
||||||
@@ -302,5 +308,16 @@ namespace Managing.Application.Scenarios
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Scenario> GetScenarioByNameAndUserAsync(string scenarioName, User user)
|
||||||
|
{
|
||||||
|
var scenario = await _tradingService.GetScenarioByNameAsync(scenarioName);
|
||||||
|
if (scenario == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Scenario {scenarioName} not found for user {user.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return scenario;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,10 @@ using MediatR;
|
|||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Orleans.Configuration;
|
||||||
|
|
||||||
namespace Managing.Bootstrap;
|
namespace Managing.Bootstrap;
|
||||||
|
|
||||||
@@ -56,13 +59,85 @@ public static class ApiBootstrap
|
|||||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
||||||
|
|
||||||
return services
|
return services
|
||||||
.AddApplication()
|
.AddApplication()
|
||||||
.AddInfrastructure(configuration)
|
.AddInfrastructure(configuration)
|
||||||
.AddWorkers(configuration)
|
.AddWorkers(configuration)
|
||||||
.AddFluentValidation()
|
.AddFluentValidation()
|
||||||
.AddMediatR();
|
.AddMediatR()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: IClusterClient is automatically available in co-hosting scenarios
|
||||||
|
// through IGrainFactory. Services should inject IGrainFactory instead of IClusterClient
|
||||||
|
// to avoid circular dependency issues during DI container construction.
|
||||||
|
|
||||||
|
public static IHostBuilder ConfigureOrleans(this IHostBuilder hostBuilder, IConfiguration configuration,
|
||||||
|
bool isProduction)
|
||||||
|
{
|
||||||
|
var postgreSqlConnectionString = configuration.GetSection("Databases:PostgreSql")["ConnectionString"];
|
||||||
|
|
||||||
|
return hostBuilder.UseOrleans(siloBuilder =>
|
||||||
|
{
|
||||||
|
// Configure clustering
|
||||||
|
if (isProduction && !string.IsNullOrEmpty(postgreSqlConnectionString))
|
||||||
|
{
|
||||||
|
// Production clustering configuration
|
||||||
|
siloBuilder
|
||||||
|
.UseAdoNetClustering(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
})
|
||||||
|
.UseAdoNetReminderService(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Development clustering configuration
|
||||||
|
siloBuilder.UseLocalhostClustering();
|
||||||
|
}
|
||||||
|
|
||||||
|
siloBuilder
|
||||||
|
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information));
|
||||||
|
|
||||||
|
// Only enable dashboard in development to avoid shutdown issues
|
||||||
|
if (!isProduction)
|
||||||
|
{
|
||||||
|
siloBuilder.UseDashboard(options =>
|
||||||
|
{
|
||||||
|
// Configure dashboard with proper shutdown handling
|
||||||
|
options.Port = 9999;
|
||||||
|
options.HostSelf = true;
|
||||||
|
options.CounterUpdateIntervalMs = 10000; // 10 seconds
|
||||||
|
options.HideTrace = true; // Hide trace to reduce dashboard overhead
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
siloBuilder.AddMemoryGrainStorageAsDefault()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
// Register existing services for Orleans DI
|
||||||
|
// These will be available to grains through dependency injection
|
||||||
|
services.AddTransient<IExchangeService, ExchangeService>();
|
||||||
|
services.AddTransient<IAccountService, AccountService>();
|
||||||
|
services.AddTransient<ITradingService, TradingService>();
|
||||||
|
services.AddTransient<IMessengerService, MessengerService>();
|
||||||
|
services.AddTransient<IBackupBotService, BackupBotService>();
|
||||||
|
})
|
||||||
|
.Configure<ClusterOptions>(options =>
|
||||||
|
{
|
||||||
|
// Configure cluster options
|
||||||
|
options.ServiceId = "ManagingApp";
|
||||||
|
options.ClusterId = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Development";
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static IServiceCollection AddApplication(this IServiceCollection services)
|
private static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<ITradingService, TradingService>();
|
services.AddScoped<ITradingService, TradingService>();
|
||||||
|
|||||||
@@ -7,22 +7,28 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1"/>
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0"/>
|
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0"/>
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0"/>
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Client" Version="9.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Clustering.AdoNet" Version="9.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Reminders.AdoNet" Version="9.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Server" Version="9.2.1" />
|
||||||
|
<PackageReference Include="OrleansDashboard" Version="8.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Application.Workers\Managing.Application.Workers.csproj"/>
|
<ProjectReference Include="..\Managing.Application.Workers\Managing.Application.Workers.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj"/>
|
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Messengers\Managing.Infrastructure.Messengers.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Messengers\Managing.Infrastructure.Messengers.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Storage\Managing.Infrastructure.Storage.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Storage\Managing.Infrastructure.Storage.csproj" />
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Web3\Managing.Infrastructure.Evm.csproj"/>
|
<ProjectReference Include="..\Managing.Infrastructure.Web3\Managing.Infrastructure.Evm.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public static class WorkersBootstrap
|
|||||||
services.AddScoped<IWorkerService, WorkerService>();
|
services.AddScoped<IWorkerService, WorkerService>();
|
||||||
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
|
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
|
||||||
services.AddScoped<ISynthApiClient, SynthApiClient>();
|
services.AddScoped<ISynthApiClient, SynthApiClient>();
|
||||||
|
services.AddScoped<IPricesService, PricesService>();
|
||||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
||||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
using Managing.Domain.Users;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using Managing.Domain.Users;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Accounts;
|
namespace Managing.Domain.Accounts;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class Account
|
public class Account
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
[Required] public string Name { get; set; }
|
[Required] public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public TradingExchanges Exchange { get; set; }
|
[Required] public TradingExchanges Exchange { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public AccountType Type { get; set; }
|
[Required] public AccountType Type { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
public string Secret { get; set; }
|
public string Secret { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
public List<Balance> Balances { get; set; }
|
public List<Balance> Balances { get; set; }
|
||||||
|
|
||||||
public bool IsPrivyWallet => Type == AccountType.Privy;
|
public bool IsPrivyWallet => Type == AccountType.Privy;
|
||||||
|
|||||||
@@ -1,14 +1,29 @@
|
|||||||
using Managing.Domain.Evm;
|
using Managing.Domain.Evm;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Accounts;
|
namespace Managing.Domain.Accounts;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class Balance
|
public class Balance
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
public string TokenImage { get; set; }
|
public string TokenImage { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public string TokenName { get; set; }
|
public string TokenName { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
public decimal Value { get; set; }
|
public decimal Value { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
public string TokenAdress { get; set; }
|
public string TokenAdress { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
public Chain Chain { get; set; }
|
public Chain Chain { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,26 @@
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Backtests;
|
namespace Managing.Domain.Backtests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight backtest class for Orleans serialization
|
||||||
|
/// Contains only the essential properties needed for backtest results
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
public class LightBacktest
|
public class LightBacktest
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = string.Empty;
|
[Id(0)] public string Id { get; set; } = string.Empty;
|
||||||
public TradingBotConfig Config { get; set; } = new();
|
[Id(1)] public TradingBotConfig Config { get; set; } = new();
|
||||||
public decimal FinalPnl { get; set; }
|
[Id(2)] public decimal FinalPnl { get; set; }
|
||||||
public int WinRate { get; set; }
|
[Id(3)] public int WinRate { get; set; }
|
||||||
public decimal GrowthPercentage { get; set; }
|
[Id(4)] public decimal GrowthPercentage { get; set; }
|
||||||
public decimal HodlPercentage { get; set; }
|
[Id(5)] public decimal HodlPercentage { get; set; }
|
||||||
public DateTime StartDate { get; set; }
|
[Id(6)] public DateTime StartDate { get; set; }
|
||||||
public DateTime EndDate { get; set; }
|
[Id(7)] public DateTime EndDate { get; set; }
|
||||||
public decimal? MaxDrawdown { get; set; }
|
[Id(8)] public decimal? MaxDrawdown { get; set; }
|
||||||
public decimal Fees { get; set; }
|
[Id(9)] public decimal Fees { get; set; }
|
||||||
public double? SharpeRatio { get; set; }
|
[Id(10)] public double? SharpeRatio { get; set; }
|
||||||
public double Score { get; set; }
|
[Id(11)] public double Score { get; set; }
|
||||||
public string ScoreMessage { get; set; } = string.Empty;
|
[Id(12)] public string ScoreMessage { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,26 @@
|
|||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Bots;
|
namespace Managing.Domain.Bots;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class BotBackup
|
public class BotBackup
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
public string Identifier { get; set; }
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public TradingBotBackup Data { get; set; }
|
public TradingBotBackup Data { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public BotStatus LastStatus { get; set; }
|
public BotStatus LastStatus { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
public DateTime CreateDate { get; set; }
|
public DateTime CreateDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,36 +1,44 @@
|
|||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Bots;
|
namespace Managing.Domain.Bots;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class TradingBotBackup
|
public class TradingBotBackup
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The complete trading bot configuration
|
/// The complete trading bot configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(0)]
|
||||||
public TradingBotConfig Config { get; set; }
|
public TradingBotConfig Config { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime state: Active signals for the bot
|
/// Runtime state: Active signals for the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(1)]
|
||||||
public HashSet<LightSignal> Signals { get; set; }
|
public HashSet<LightSignal> Signals { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime state: Open and closed positions for the bot
|
/// Runtime state: Open and closed positions for the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(2)]
|
||||||
public List<Position> Positions { get; set; }
|
public List<Position> Positions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime state: Historical wallet balances over time
|
/// Runtime state: Historical wallet balances over time
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(3)]
|
||||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime state: When the bot was started
|
/// Runtime state: When the bot was started
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(4)]
|
||||||
public DateTime StartupTime { get; set; }
|
public DateTime StartupTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime state: When the bot was created
|
/// Runtime state: When the bot was created
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(5)]
|
||||||
public DateTime CreateDate { get; set; }
|
public DateTime CreateDate { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,45 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Domain.Risk;
|
using Managing.Domain.Risk;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Bots;
|
namespace Managing.Domain.Bots;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class TradingBotConfig
|
public class TradingBotConfig
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
[Required] public string AccountName { get; set; }
|
[Required] public string AccountName { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public LightMoneyManagement MoneyManagement { get; set; }
|
[Required] public LightMoneyManagement MoneyManagement { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public Ticker Ticker { get; set; }
|
[Required] public Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public Timeframe Timeframe { get; set; }
|
[Required] public Timeframe Timeframe { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public bool IsForWatchingOnly { get; set; }
|
[Required] public bool IsForWatchingOnly { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
[Required] public decimal BotTradingBalance { get; set; }
|
[Required] public decimal BotTradingBalance { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
[Required] public bool IsForBacktest { get; set; }
|
[Required] public bool IsForBacktest { get; set; }
|
||||||
|
|
||||||
|
[Id(7)]
|
||||||
[Required] public int CooldownPeriod { get; set; }
|
[Required] public int CooldownPeriod { get; set; }
|
||||||
|
|
||||||
|
[Id(8)]
|
||||||
[Required] public int MaxLossStreak { get; set; }
|
[Required] public int MaxLossStreak { get; set; }
|
||||||
|
|
||||||
|
[Id(9)]
|
||||||
[Required] public bool FlipPosition { get; set; }
|
[Required] public bool FlipPosition { get; set; }
|
||||||
|
|
||||||
|
[Id(10)]
|
||||||
[Required] public string Name { get; set; }
|
[Required] public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -24,23 +47,28 @@ public class TradingBotConfig
|
|||||||
/// Contains all configurable parameters for Expected Utility Theory, Kelly Criterion, and probability thresholds.
|
/// Contains all configurable parameters for Expected Utility Theory, Kelly Criterion, and probability thresholds.
|
||||||
/// If null, default risk management settings will be used.
|
/// If null, default risk management settings will be used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(11)]
|
||||||
public RiskManagement RiskManagement { get; set; } = new();
|
public RiskManagement RiskManagement { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The scenario object containing all strategies. When provided, this takes precedence over ScenarioName.
|
/// The lightweight scenario object containing all strategies. When provided, this takes precedence over ScenarioName.
|
||||||
/// This allows running backtests without requiring scenarios to be saved in the database.
|
/// This allows running backtests without requiring scenarios to be saved in the database.
|
||||||
|
/// Orleans-friendly version without FixedSizeQueue and User properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Scenario Scenario { get; set; }
|
[Id(12)]
|
||||||
|
public LightScenario Scenario { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The scenario name to load from database. Only used when Scenario object is not provided.
|
/// The scenario name to load from database. Only used when Scenario object is not provided.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(13)]
|
||||||
public string ScenarioName { get; set; }
|
public string ScenarioName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum time in hours that a position can remain open before being automatically closed.
|
/// Maximum time in hours that a position can remain open before being automatically closed.
|
||||||
/// If null, time-based position closure is disabled.
|
/// If null, time-based position closure is disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(14)]
|
||||||
public decimal? MaxPositionTimeHours { get; set; }
|
public decimal? MaxPositionTimeHours { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,6 +77,7 @@ public class TradingBotConfig
|
|||||||
/// If false, the position will only be closed when MaxPositionTimeHours is reached.
|
/// If false, the position will only be closed when MaxPositionTimeHours is reached.
|
||||||
/// Default is false to maintain existing behavior.
|
/// Default is false to maintain existing behavior.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(15)]
|
||||||
public bool CloseEarlyWhenProfitable { get; set; } = false;
|
public bool CloseEarlyWhenProfitable { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -56,6 +85,7 @@ public class TradingBotConfig
|
|||||||
/// If false, positions will be flipped regardless of profit status.
|
/// If false, positions will be flipped regardless of profit status.
|
||||||
/// Default is true for safer trading.
|
/// Default is true for safer trading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(16)]
|
||||||
[Required]
|
[Required]
|
||||||
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
||||||
|
|
||||||
@@ -65,20 +95,24 @@ public class TradingBotConfig
|
|||||||
/// When false, the bot operates in traditional mode without Synth predictions.
|
/// When false, the bot operates in traditional mode without Synth predictions.
|
||||||
/// The actual Synth configuration is managed centrally in SynthPredictionService.
|
/// The actual Synth configuration is managed centrally in SynthPredictionService.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(17)]
|
||||||
public bool UseSynthApi { get; set; } = false;
|
public bool UseSynthApi { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to use Synth predictions for position sizing adjustments and risk assessment
|
/// Whether to use Synth predictions for position sizing adjustments and risk assessment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(18)]
|
||||||
public bool UseForPositionSizing { get; set; } = true;
|
public bool UseForPositionSizing { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to use Synth predictions for signal filtering
|
/// Whether to use Synth predictions for signal filtering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(19)]
|
||||||
public bool UseForSignalFiltering { get; set; } = true;
|
public bool UseForSignalFiltering { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
|
/// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(20)]
|
||||||
public bool UseForDynamicStopLoss { get; set; } = true;
|
public bool UseForDynamicStopLoss { get; set; } = true;
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,41 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
|
using Orleans;
|
||||||
using Skender.Stock.Indicators;
|
using Skender.Stock.Indicators;
|
||||||
|
|
||||||
namespace Managing.Domain.Candles
|
namespace Managing.Domain.Candles
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public class Candle : IQuote
|
public class Candle : IQuote
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
[Required] public Enums.TradingExchanges Exchange { get; set; }
|
[Required] public Enums.TradingExchanges Exchange { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public string Ticker { get; set; }
|
[Required] public string Ticker { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public DateTime OpenTime { get; set; }
|
[Required] public DateTime OpenTime { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public DateTime Date { get; set; }
|
[Required] public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public decimal Open { get; set; }
|
[Required] public decimal Open { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
[Required] public decimal Close { get; set; }
|
[Required] public decimal Close { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
[Required] public decimal High { get; set; }
|
[Required] public decimal High { get; set; }
|
||||||
|
|
||||||
|
[Id(7)]
|
||||||
[Required] public decimal Low { get; set; }
|
[Required] public decimal Low { get; set; }
|
||||||
|
|
||||||
|
[Id(8)]
|
||||||
[Required] public Enums.Timeframe Timeframe { get; set; }
|
[Required] public Enums.Timeframe Timeframe { get; set; }
|
||||||
|
|
||||||
|
[Id(9)]
|
||||||
public decimal Volume { get; set; }
|
public decimal Volume { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,19 @@
|
|||||||
namespace Managing.Domain.Evm;
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Evm;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class Chain
|
public class Chain
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public string RpcUrl { get; set; }
|
public string RpcUrl { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public int ChainId { get; set; }
|
public int ChainId { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Exilion.TradingAtomics" Version="1.0.4"/>
|
<PackageReference Include="Exilion.TradingAtomics" Version="1.0.4"/>
|
||||||
|
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="9.2.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
|
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class LightMoneyManagement
|
public class LightMoneyManagement
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
[Required] public string Name { get; set; }
|
[Required] public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public Timeframe Timeframe { get; set; }
|
[Required] public Timeframe Timeframe { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public decimal StopLoss { get; set; }
|
[Required] public decimal StopLoss { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public decimal TakeProfit { get; set; }
|
[Required] public decimal TakeProfit { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public decimal Leverage { get; set; }
|
[Required] public decimal Leverage { get; set; }
|
||||||
|
|
||||||
public void FormatPercentage()
|
public void FormatPercentage()
|
||||||
{
|
{
|
||||||
StopLoss /= 100;
|
StopLoss /= 100;
|
||||||
TakeProfit /= 100;
|
TakeProfit /= 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.MoneyManagements
|
namespace Managing.Domain.MoneyManagements
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public class MoneyManagement : LightMoneyManagement
|
public class MoneyManagement : LightMoneyManagement
|
||||||
{
|
{
|
||||||
|
[Id(5)]
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Risk;
|
namespace Managing.Domain.Risk;
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ namespace Managing.Domain.Risk;
|
|||||||
/// Risk management configuration for trading bots
|
/// Risk management configuration for trading bots
|
||||||
/// Contains all configurable risk parameters for probabilistic analysis and position sizing
|
/// Contains all configurable risk parameters for probabilistic analysis and position sizing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
public class RiskManagement
|
public class RiskManagement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -14,6 +16,7 @@ public class RiskManagement
|
|||||||
/// Signals with SL probability above this threshold may be filtered out
|
/// Signals with SL probability above this threshold may be filtered out
|
||||||
/// Range: 0.05 (5%) to 0.50 (50%)
|
/// Range: 0.05 (5%) to 0.50 (50%)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(0)]
|
||||||
[Range(0.05, 0.50)]
|
[Range(0.05, 0.50)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal AdverseProbabilityThreshold { get; set; } = 0.20m;
|
public decimal AdverseProbabilityThreshold { get; set; } = 0.20m;
|
||||||
@@ -23,6 +26,7 @@ public class RiskManagement
|
|||||||
/// Used for additional signal filtering and confidence assessment
|
/// Used for additional signal filtering and confidence assessment
|
||||||
/// Range: 0.10 (10%) to 0.70 (70%)
|
/// Range: 0.10 (10%) to 0.70 (70%)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(1)]
|
||||||
[Range(0.10, 0.70)]
|
[Range(0.10, 0.70)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal FavorableProbabilityThreshold { get; set; } = 0.30m;
|
public decimal FavorableProbabilityThreshold { get; set; } = 0.30m;
|
||||||
@@ -32,6 +36,7 @@ public class RiskManagement
|
|||||||
/// Higher values = more risk-averse behavior in utility calculations
|
/// Higher values = more risk-averse behavior in utility calculations
|
||||||
/// Range: 0.1 (risk-seeking) to 5.0 (highly risk-averse)
|
/// Range: 0.1 (risk-seeking) to 5.0 (highly risk-averse)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(2)]
|
||||||
[Range(0.1, 5.0)]
|
[Range(0.1, 5.0)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal RiskAversion { get; set; } = 1.0m;
|
public decimal RiskAversion { get; set; } = 1.0m;
|
||||||
@@ -41,6 +46,7 @@ public class RiskManagement
|
|||||||
/// Trades with Kelly fraction below this threshold are considered unfavorable
|
/// Trades with Kelly fraction below this threshold are considered unfavorable
|
||||||
/// Range: 0.5% to 10%
|
/// Range: 0.5% to 10%
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(3)]
|
||||||
[Range(0.005, 0.10)]
|
[Range(0.005, 0.10)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal KellyMinimumThreshold { get; set; } = 0.01m;
|
public decimal KellyMinimumThreshold { get; set; } = 0.01m;
|
||||||
@@ -50,6 +56,7 @@ public class RiskManagement
|
|||||||
/// Prevents over-allocation even when Kelly suggests higher percentages
|
/// Prevents over-allocation even when Kelly suggests higher percentages
|
||||||
/// Range: 5% to 50%
|
/// Range: 5% to 50%
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(4)]
|
||||||
[Range(0.05, 0.50)]
|
[Range(0.05, 0.50)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal KellyMaximumCap { get; set; } = 0.25m;
|
public decimal KellyMaximumCap { get; set; } = 0.25m;
|
||||||
@@ -59,6 +66,7 @@ public class RiskManagement
|
|||||||
/// Positions with higher liquidation risk may be blocked or reduced
|
/// Positions with higher liquidation risk may be blocked or reduced
|
||||||
/// Range: 5% to 30%
|
/// Range: 5% to 30%
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(5)]
|
||||||
[Range(0.05, 0.30)]
|
[Range(0.05, 0.30)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal MaxLiquidationProbability { get; set; } = 0.10m;
|
public decimal MaxLiquidationProbability { get; set; } = 0.10m;
|
||||||
@@ -68,6 +76,7 @@ public class RiskManagement
|
|||||||
/// Longer horizons provide more stable predictions but less responsive signals
|
/// Longer horizons provide more stable predictions but less responsive signals
|
||||||
/// Range: 1 hour to 168 hours (1 week)
|
/// Range: 1 hour to 168 hours (1 week)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(6)]
|
||||||
[Range(1, 168)]
|
[Range(1, 168)]
|
||||||
[Required]
|
[Required]
|
||||||
public int SignalValidationTimeHorizonHours { get; set; } = 24;
|
public int SignalValidationTimeHorizonHours { get; set; } = 24;
|
||||||
@@ -77,6 +86,7 @@ public class RiskManagement
|
|||||||
/// Shorter horizons for more frequent risk updates on open positions
|
/// Shorter horizons for more frequent risk updates on open positions
|
||||||
/// Range: 1 hour to 48 hours
|
/// Range: 1 hour to 48 hours
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(7)]
|
||||||
[Range(1, 48)]
|
[Range(1, 48)]
|
||||||
[Required]
|
[Required]
|
||||||
public int PositionMonitoringTimeHorizonHours { get; set; } = 6;
|
public int PositionMonitoringTimeHorizonHours { get; set; } = 6;
|
||||||
@@ -86,6 +96,7 @@ public class RiskManagement
|
|||||||
/// Positions exceeding this liquidation risk will trigger warnings
|
/// Positions exceeding this liquidation risk will trigger warnings
|
||||||
/// Range: 10% to 40%
|
/// Range: 10% to 40%
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(8)]
|
||||||
[Range(0.10, 0.40)]
|
[Range(0.10, 0.40)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal PositionWarningThreshold { get; set; } = 0.20m;
|
public decimal PositionWarningThreshold { get; set; } = 0.20m;
|
||||||
@@ -95,6 +106,7 @@ public class RiskManagement
|
|||||||
/// Positions exceeding this liquidation risk will be automatically closed
|
/// Positions exceeding this liquidation risk will be automatically closed
|
||||||
/// Range: 30% to 80%
|
/// Range: 30% to 80%
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(9)]
|
||||||
[Range(0.30, 0.80)]
|
[Range(0.30, 0.80)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal PositionAutoCloseThreshold { get; set; } = 0.50m;
|
public decimal PositionAutoCloseThreshold { get; set; } = 0.50m;
|
||||||
@@ -104,6 +116,7 @@ public class RiskManagement
|
|||||||
/// Values less than 1.0 implement fractional Kelly (e.g., 0.5 = half-Kelly)
|
/// Values less than 1.0 implement fractional Kelly (e.g., 0.5 = half-Kelly)
|
||||||
/// Range: 0.1 to 1.0
|
/// Range: 0.1 to 1.0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(10)]
|
||||||
[Range(0.1, 1.0)]
|
[Range(0.1, 1.0)]
|
||||||
[Required]
|
[Required]
|
||||||
public decimal KellyFractionalMultiplier { get; set; } = 1.0m;
|
public decimal KellyFractionalMultiplier { get; set; } = 1.0m;
|
||||||
@@ -111,18 +124,21 @@ public class RiskManagement
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Risk tolerance level affecting overall risk calculations
|
/// Risk tolerance level affecting overall risk calculations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(11)]
|
||||||
[Required]
|
[Required]
|
||||||
public Enums.RiskToleranceLevel RiskTolerance { get; set; } = Enums.RiskToleranceLevel.Moderate;
|
public Enums.RiskToleranceLevel RiskTolerance { get; set; } = Enums.RiskToleranceLevel.Moderate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to use Expected Utility Theory for decision making
|
/// Whether to use Expected Utility Theory for decision making
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(12)]
|
||||||
[Required]
|
[Required]
|
||||||
public bool UseExpectedUtility { get; set; } = true;
|
public bool UseExpectedUtility { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to use Kelly Criterion for position sizing recommendations
|
/// Whether to use Kelly Criterion for position sizing recommendations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Id(13)]
|
||||||
[Required]
|
[Required]
|
||||||
public bool UseKellyCriterion { get; set; } = true;
|
public bool UseKellyCriterion { get; set; } = true;
|
||||||
|
|
||||||
|
|||||||
57
src/Managing.Domain/Scenarios/LightScenario.cs
Normal file
57
src/Managing.Domain/Scenarios/LightScenario.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using Managing.Domain.Strategies;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Scenarios;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight scenario class for Orleans serialization
|
||||||
|
/// Contains only the essential properties needed for backtesting
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class LightScenario
|
||||||
|
{
|
||||||
|
public LightScenario(string name, int? loopbackPeriod = 1)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Indicators = new List<LightIndicator>();
|
||||||
|
LoopbackPeriod = loopbackPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Id(0)] public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)] public List<LightIndicator> Indicators { get; set; }
|
||||||
|
|
||||||
|
[Id(2)] public int? LoopbackPeriod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a full Scenario to a LightScenario
|
||||||
|
/// </summary>
|
||||||
|
public static LightScenario FromScenario(Scenario scenario)
|
||||||
|
{
|
||||||
|
var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod)
|
||||||
|
{
|
||||||
|
Indicators = scenario.Indicators?.Select(LightIndicator.FromIndicator).ToList() ??
|
||||||
|
new List<LightIndicator>()
|
||||||
|
};
|
||||||
|
return lightScenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a LightScenario back to a full Scenario
|
||||||
|
/// </summary>
|
||||||
|
public Scenario ToScenario()
|
||||||
|
{
|
||||||
|
var scenario = new Scenario(Name, LoopbackPeriod)
|
||||||
|
{
|
||||||
|
Indicators = Indicators?.Select(li => li.ToIndicator()).ToList() ?? new List<Indicator>()
|
||||||
|
};
|
||||||
|
return scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddIndicator(LightIndicator indicator)
|
||||||
|
{
|
||||||
|
if (Indicators == null)
|
||||||
|
Indicators = new List<LightIndicator>();
|
||||||
|
Indicators.Add(indicator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Scenarios
|
namespace Managing.Domain.Scenarios
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public class Scenario
|
public class Scenario
|
||||||
{
|
{
|
||||||
public Scenario(string name, int? loopbackPeriod = 1)
|
public Scenario(string name, int? loopbackPeriod = 1)
|
||||||
@@ -12,9 +14,16 @@ namespace Managing.Domain.Scenarios
|
|||||||
LoopbackPeriod = loopbackPeriod;
|
LoopbackPeriod = loopbackPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Id(0)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public List<Indicator> Indicators { get; set; }
|
public List<Indicator> Indicators { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public int? LoopbackPeriod { get; set; }
|
public int? LoopbackPeriod { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
public void AddIndicator(Indicator indicator)
|
public void AddIndicator(Indicator indicator)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Runtime.Serialization;
|
using Managing.Core.FixedSizedQueue;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Managing.Core.FixedSizedQueue;
|
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Strategies.Base;
|
using Managing.Domain.Strategies.Base;
|
||||||
@@ -20,18 +18,31 @@ namespace Managing.Domain.Strategies
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
[JsonIgnore] [IgnoreDataMember] public FixedSizeQueue<Candle> Candles { get; set; }
|
|
||||||
|
public FixedSizeQueue<Candle> Candles { get; set; }
|
||||||
|
|
||||||
public IndicatorType Type { get; set; }
|
public IndicatorType Type { get; set; }
|
||||||
|
|
||||||
public SignalType SignalType { get; set; }
|
public SignalType SignalType { get; set; }
|
||||||
|
|
||||||
public int MinimumHistory { get; set; }
|
public int MinimumHistory { get; set; }
|
||||||
|
|
||||||
public int? Period { get; set; }
|
public int? Period { get; set; }
|
||||||
|
|
||||||
public int? FastPeriods { get; set; }
|
public int? FastPeriods { get; set; }
|
||||||
|
|
||||||
public int? SlowPeriods { get; set; }
|
public int? SlowPeriods { get; set; }
|
||||||
|
|
||||||
public int? SignalPeriods { get; set; }
|
public int? SignalPeriods { get; set; }
|
||||||
|
|
||||||
public double? Multiplier { get; set; }
|
public double? Multiplier { get; set; }
|
||||||
|
|
||||||
public int? SmoothPeriods { get; set; }
|
public int? SmoothPeriods { get; set; }
|
||||||
|
|
||||||
public int? StochPeriods { get; set; }
|
public int? StochPeriods { get; set; }
|
||||||
|
|
||||||
public int? CyclePeriods { get; set; }
|
public int? CyclePeriods { get; set; }
|
||||||
|
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
public virtual List<LightSignal> Run()
|
public virtual List<LightSignal> Run()
|
||||||
|
|||||||
84
src/Managing.Domain/Strategies/LightIndicator.cs
Normal file
84
src/Managing.Domain/Strategies/LightIndicator.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using Managing.Domain.Scenarios;
|
||||||
|
using Orleans;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Strategies;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight indicator class for Orleans serialization
|
||||||
|
/// Contains only the essential properties needed for backtesting
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class LightIndicator
|
||||||
|
{
|
||||||
|
public LightIndicator(string name, IndicatorType type)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
SignalType = ScenarioHelpers.GetSignalType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Id(0)] public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)] public IndicatorType Type { get; set; }
|
||||||
|
|
||||||
|
[Id(2)] public SignalType SignalType { get; set; }
|
||||||
|
|
||||||
|
[Id(3)] public int MinimumHistory { get; set; }
|
||||||
|
|
||||||
|
[Id(4)] public int? Period { get; set; }
|
||||||
|
|
||||||
|
[Id(5)] public int? FastPeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(6)] public int? SlowPeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(7)] public int? SignalPeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(8)] public double? Multiplier { get; set; }
|
||||||
|
|
||||||
|
[Id(9)] public int? SmoothPeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(10)] public int? StochPeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(11)] public int? CyclePeriods { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a full Indicator to a LightIndicator
|
||||||
|
/// </summary>
|
||||||
|
public static LightIndicator FromIndicator(Indicator indicator)
|
||||||
|
{
|
||||||
|
return new LightIndicator(indicator.Name, indicator.Type)
|
||||||
|
{
|
||||||
|
SignalType = indicator.SignalType,
|
||||||
|
MinimumHistory = indicator.MinimumHistory,
|
||||||
|
Period = indicator.Period,
|
||||||
|
FastPeriods = indicator.FastPeriods,
|
||||||
|
SlowPeriods = indicator.SlowPeriods,
|
||||||
|
SignalPeriods = indicator.SignalPeriods,
|
||||||
|
Multiplier = indicator.Multiplier,
|
||||||
|
SmoothPeriods = indicator.SmoothPeriods,
|
||||||
|
StochPeriods = indicator.StochPeriods,
|
||||||
|
CyclePeriods = indicator.CyclePeriods
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a LightIndicator back to a full Indicator
|
||||||
|
/// </summary>
|
||||||
|
public Indicator ToIndicator()
|
||||||
|
{
|
||||||
|
return new Indicator(Name, Type)
|
||||||
|
{
|
||||||
|
SignalType = SignalType,
|
||||||
|
MinimumHistory = MinimumHistory,
|
||||||
|
Period = Period,
|
||||||
|
FastPeriods = FastPeriods,
|
||||||
|
SlowPeriods = SlowPeriods,
|
||||||
|
SignalPeriods = SignalPeriods,
|
||||||
|
Multiplier = Multiplier,
|
||||||
|
SmoothPeriods = SmoothPeriods,
|
||||||
|
StochPeriods = StochPeriods,
|
||||||
|
CyclePeriods = CyclePeriods
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class LightSignal : ValueObject
|
public class LightSignal : ValueObject
|
||||||
{
|
{
|
||||||
public LightSignal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
|
public LightSignal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
|
||||||
@@ -24,17 +26,40 @@ public class LightSignal : ValueObject
|
|||||||
$"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
|
$"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Id(0)]
|
||||||
[Required] public SignalStatus Status { get; set; }
|
[Required] public SignalStatus Status { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public TradeDirection Direction { get; }
|
[Required] public TradeDirection Direction { get; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public Confidence Confidence { get; set; }
|
[Required] public Confidence Confidence { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public Timeframe Timeframe { get; }
|
[Required] public Timeframe Timeframe { get; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public DateTime Date { get; private set; }
|
[Required] public DateTime Date { get; private set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
[Required] public Candle Candle { get; }
|
[Required] public Candle Candle { get; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
[Required] public string Identifier { get; }
|
[Required] public string Identifier { get; }
|
||||||
|
|
||||||
|
[Id(7)]
|
||||||
[Required] public Ticker Ticker { get; }
|
[Required] public Ticker Ticker { get; }
|
||||||
|
|
||||||
|
[Id(8)]
|
||||||
[Required] public TradingExchanges Exchange { get; set; }
|
[Required] public TradingExchanges Exchange { get; set; }
|
||||||
|
|
||||||
|
[Id(9)]
|
||||||
[Required] public IndicatorType IndicatorType { get; set; }
|
[Required] public IndicatorType IndicatorType { get; set; }
|
||||||
|
|
||||||
|
[Id(10)]
|
||||||
[Required] public SignalType SignalType { get; set; }
|
[Required] public SignalType SignalType { get; set; }
|
||||||
|
|
||||||
|
[Id(11)]
|
||||||
[Required] public string IndicatorName { get; set; }
|
[Required] public string IndicatorName { get; set; }
|
||||||
|
|
||||||
protected override IEnumerable<object> GetEqualityComponents()
|
protected override IEnumerable<object> GetEqualityComponents()
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Trades
|
namespace Managing.Domain.Trades
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public class Position
|
public class Position
|
||||||
{
|
{
|
||||||
public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker,
|
public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker,
|
||||||
@@ -21,28 +23,53 @@ namespace Managing.Domain.Trades
|
|||||||
User = user;
|
User = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Id(0)]
|
||||||
[Required] public string AccountName { get; set; }
|
[Required] public string AccountName { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public DateTime Date { get; set; }
|
[Required] public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public TradeDirection OriginDirection { get; set; }
|
[Required] public TradeDirection OriginDirection { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public Ticker Ticker { get; set; }
|
[Required] public Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public LightMoneyManagement MoneyManagement { get; set; }
|
[Required] public LightMoneyManagement MoneyManagement { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
[Required] [JsonPropertyName("Open")] public Trade Open { get; set; }
|
[Required] [JsonPropertyName("Open")] public Trade Open { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
[Required]
|
[Required]
|
||||||
[JsonPropertyName("StopLoss")]
|
[JsonPropertyName("StopLoss")]
|
||||||
public Trade StopLoss { get; set; }
|
public Trade StopLoss { get; set; }
|
||||||
|
|
||||||
|
[Id(7)]
|
||||||
[Required]
|
[Required]
|
||||||
[JsonPropertyName("TakeProfit1")]
|
[JsonPropertyName("TakeProfit1")]
|
||||||
public Trade TakeProfit1 { get; set; }
|
public Trade TakeProfit1 { get; set; }
|
||||||
|
|
||||||
|
[Id(8)]
|
||||||
[JsonPropertyName("TakeProfit2")] public Trade TakeProfit2 { get; set; }
|
[JsonPropertyName("TakeProfit2")] public Trade TakeProfit2 { get; set; }
|
||||||
|
|
||||||
|
[Id(9)]
|
||||||
[JsonPropertyName("ProfitAndLoss")] public ProfitAndLoss ProfitAndLoss { get; set; }
|
[JsonPropertyName("ProfitAndLoss")] public ProfitAndLoss ProfitAndLoss { get; set; }
|
||||||
|
|
||||||
|
[Id(10)]
|
||||||
[Required] public PositionStatus Status { get; set; }
|
[Required] public PositionStatus Status { get; set; }
|
||||||
|
|
||||||
|
[Id(11)]
|
||||||
public string SignalIdentifier { get; set; }
|
public string SignalIdentifier { get; set; }
|
||||||
|
|
||||||
|
[Id(12)]
|
||||||
[Required] public string Identifier { get; set; }
|
[Required] public string Identifier { get; set; }
|
||||||
|
|
||||||
|
[Id(13)]
|
||||||
[Required] public PositionInitiator Initiator { get; set; }
|
[Required] public PositionInitiator Initiator { get; set; }
|
||||||
|
|
||||||
|
[Id(14)]
|
||||||
[Required] public User User { get; set; }
|
[Required] public User User { get; set; }
|
||||||
|
|
||||||
public bool IsFinished()
|
public bool IsFinished()
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
using static Managing.Common.Enums;
|
using Orleans;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Trades
|
namespace Managing.Domain.Trades
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public sealed class ProfitAndLoss
|
public sealed class ProfitAndLoss
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
public decimal Realized { get; set; }
|
public decimal Realized { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public decimal Net { get; set; }
|
public decimal Net { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public decimal AverageOpenPrice { get; private set; }
|
public decimal AverageOpenPrice { get; private set; }
|
||||||
private const decimal _multiplier = 100000;
|
private const decimal _multiplier = 100000;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Orleans;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Trades
|
namespace Managing.Domain.Trades
|
||||||
{
|
{
|
||||||
|
[GenerateSerializer]
|
||||||
public class Trade
|
public class Trade
|
||||||
{
|
{
|
||||||
public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker,
|
public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker,
|
||||||
@@ -21,16 +23,37 @@ namespace Managing.Domain.Trades
|
|||||||
Fee = 0;
|
Fee = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Id(0)]
|
||||||
[Required] public decimal Fee { get; set; }
|
[Required] public decimal Fee { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
[Required] public DateTime Date { get; set; }
|
[Required] public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
[Required] public TradeDirection Direction { get; set; }
|
[Required] public TradeDirection Direction { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
[Required] public TradeStatus Status { get; set; }
|
[Required] public TradeStatus Status { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
[Required] public TradeType TradeType { get; set; }
|
[Required] public TradeType TradeType { get; set; }
|
||||||
|
|
||||||
|
[Id(5)]
|
||||||
[Required] public Ticker Ticker { get; set; }
|
[Required] public Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
[Id(6)]
|
||||||
[Required] public decimal Quantity { get; set; }
|
[Required] public decimal Quantity { get; set; }
|
||||||
|
|
||||||
|
[Id(7)]
|
||||||
[Required] public decimal Price { get; set; }
|
[Required] public decimal Price { get; set; }
|
||||||
|
|
||||||
|
[Id(8)]
|
||||||
[Required] public decimal Leverage { get; set; }
|
[Required] public decimal Leverage { get; set; }
|
||||||
|
|
||||||
|
[Id(9)]
|
||||||
[Required] public string ExchangeOrderId { get; set; }
|
[Required] public string ExchangeOrderId { get; set; }
|
||||||
|
|
||||||
|
[Id(10)]
|
||||||
[Required] public string Message { get; set; }
|
[Required] public string Message { get; set; }
|
||||||
|
|
||||||
public void SetStatus(TradeStatus status)
|
public void SetStatus(TradeStatus status)
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
|
using Orleans;
|
||||||
|
|
||||||
namespace Managing.Domain.Users;
|
namespace Managing.Domain.Users;
|
||||||
|
|
||||||
|
[GenerateSerializer]
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
|
[Id(0)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Id(1)]
|
||||||
public List<Account> Accounts { get; set; }
|
public List<Account> Accounts { get; set; }
|
||||||
|
|
||||||
|
[Id(2)]
|
||||||
public string AgentName { get; set; }
|
public string AgentName { get; set; }
|
||||||
|
|
||||||
|
[Id(3)]
|
||||||
public string AvatarUrl { get; set; }
|
public string AvatarUrl { get; set; }
|
||||||
|
|
||||||
|
[Id(4)]
|
||||||
public string TelegramChannel { get; set; }
|
public string TelegramChannel { get; set; }
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<PackageReference Include="FTX.Net" Version="1.0.16" />
|
<PackageReference Include="FTX.Net" Version="1.0.16" />
|
||||||
<PackageReference Include="KrakenExchange.Net" Version="4.6.5" />
|
<PackageReference Include="KrakenExchange.Net" Version="4.6.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net" Version="3.15.0"/>
|
<PackageReference Include="Discord.Net" Version="3.15.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj"/>
|
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ public class KaigenService : IKaigenService
|
|||||||
var requestPayload = new
|
var requestPayload = new
|
||||||
{
|
{
|
||||||
requestId = requestId,
|
requestId = requestId,
|
||||||
walletAddress = walletAddress,
|
|
||||||
debitAmount = debitAmount
|
debitAmount = debitAmount
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,6 +113,13 @@ public class KaigenService : IKaigenService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<KaigenResponse>(_jsonOptions);
|
var result = await response.Content.ReadFromJsonAsync<KaigenResponse>(_jsonOptions);
|
||||||
|
|
||||||
|
if (result == null || !result.Success)
|
||||||
|
{
|
||||||
|
_logger.LogError("Debit request failed: {Message}", result?.Message ?? "Unknown error");
|
||||||
|
throw new Exception($"Debit request failed: {result?.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Successfully debited {Amount} credits for user {UserName} (wallet: {WalletAddress})",
|
"Successfully debited {Amount} credits for user {UserName} (wallet: {WalletAddress})",
|
||||||
debitAmount, user.Name, walletAddress);
|
debitAmount, user.Name, walletAddress);
|
||||||
@@ -145,7 +151,6 @@ public class KaigenService : IKaigenService
|
|||||||
var requestPayload = new
|
var requestPayload = new
|
||||||
{
|
{
|
||||||
requestId = requestId,
|
requestId = requestId,
|
||||||
walletAddress = walletAddress
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
@@ -180,21 +185,21 @@ public class KaigenService : IKaigenService
|
|||||||
{
|
{
|
||||||
// Create the auth token: "walletaddress-username"
|
// Create the auth token: "walletaddress-username"
|
||||||
var authToken = $"{GetUserWalletAddress(user)}-{user.Name}";
|
var authToken = $"{GetUserWalletAddress(user)}-{user.Name}";
|
||||||
|
|
||||||
// Encrypt the auth token using AES-256-GCM
|
// Encrypt the auth token using AES-256-GCM
|
||||||
var encryptedToken = CryptoHelpers.EncryptAesGcm(authToken, _settings.SecretKey);
|
var encryptedToken = CryptoHelpers.EncryptAesGcm(authToken, _settings.SecretKey);
|
||||||
|
|
||||||
// Create Basic Auth header with the encrypted token
|
// Create Basic Auth header with the encrypted token
|
||||||
var basicAuthString = $"{encryptedToken}:";
|
var basicAuthString = $"{encryptedToken}:";
|
||||||
var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes(basicAuthString));
|
var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes(basicAuthString));
|
||||||
|
|
||||||
// Create a new request with the auth header
|
// Create a new request with the auth header
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
||||||
{
|
{
|
||||||
Content = JsonContent.Create(payload, options: _jsonOptions)
|
Content = JsonContent.Create(payload, options: _jsonOptions)
|
||||||
};
|
};
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64Auth);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64Auth);
|
||||||
|
|
||||||
return await _httpClient.SendAsync(request);
|
return await _httpClient.SendAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
VITE_API_URL_LOCAL=http://localhost:5000
|
|
||||||
VITE_API_URL_SERVER=https://dev-managing-api.apps.managing.live
|
|
||||||
VITE_WORKER_URL_LOCAL=https://localhost:5002
|
|
||||||
VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live
|
|
||||||
ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9
|
|
||||||
WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5
|
|
||||||
VITE_PRIVY_APP_ID=cm7u09v0u002zrkuf2yjjr58p
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"extends": [
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:jsx-a11y/recommended"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": [
|
|
||||||
"jsx-a11y",
|
|
||||||
"import",
|
|
||||||
"sort-keys-fix",
|
|
||||||
"react-hooks",
|
|
||||||
"@typescript-eslint",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true,
|
|
||||||
"es6": true,
|
|
||||||
"jest": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"JSX": "readonly"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"react": {
|
|
||||||
"version": "detect"
|
|
||||||
},
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts", ".tsx"]
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"node": {
|
|
||||||
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"alwaysTryTypes": true,
|
|
||||||
// always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
|
|
||||||
"project": ["tsconfig.json"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-console": "error",
|
|
||||||
"react-hooks/rules-of-hooks": "error",
|
|
||||||
"prettier/prettier": [
|
|
||||||
"warn",
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"usePrettierrc": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/order": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"object"
|
|
||||||
],
|
|
||||||
"newlines-between": "always",
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc",
|
|
||||||
"caseInsensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/named": "error",
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/no-named-as-default": "warn",
|
|
||||||
"import/no-duplicates": "error",
|
|
||||||
"sort-keys-fix/sort-keys-fix": "warn",
|
|
||||||
"@import/no-named-as-default-member": "off",
|
|
||||||
"@typescript-eslint/consistent-type-imports": "warn",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/ban-types": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"@typescript-eslint/no-empty-function": "off"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.js"],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": ["off"],
|
|
||||||
"@typescript-eslint/no-var-requires": ["off"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
src/Managing.Pinky/.gitattributes
vendored
3
src/Managing.Pinky/.gitattributes
vendored
@@ -1,3 +0,0 @@
|
|||||||
.jest/* linguist-vendored
|
|
||||||
mocks/* linguist-vendored
|
|
||||||
mockServiceWorker.js linguist-vendored
|
|
||||||
18
src/Managing.Pinky/.github/workflows/build.yml
vendored
18
src/Managing.Pinky/.github/workflows/build.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Build
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2-beta
|
|
||||||
with:
|
|
||||||
node-version: '18.1.0'
|
|
||||||
- run: yarn install
|
|
||||||
- run: yarn build
|
|
||||||
18
src/Managing.Pinky/.github/workflows/lint.yml
vendored
18
src/Managing.Pinky/.github/workflows/lint.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Lint
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2-beta
|
|
||||||
with:
|
|
||||||
node-version: '18.1.0'
|
|
||||||
- run: yarn install
|
|
||||||
- run: yarn lint
|
|
||||||
18
src/Managing.Pinky/.github/workflows/test.yml
vendored
18
src/Managing.Pinky/.github/workflows/test.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Test
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2-beta
|
|
||||||
with:
|
|
||||||
node-version: '18.1.0'
|
|
||||||
- run: yarn install
|
|
||||||
- run: yarn test
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
name: Typecheck
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
typecheck:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2-beta
|
|
||||||
with:
|
|
||||||
node-version: '18.1.0'
|
|
||||||
- run: yarn install
|
|
||||||
- run: yarn typecheck
|
|
||||||
5
src/Managing.Pinky/.gitignore
vendored
5
src/Managing.Pinky/.gitignore
vendored
@@ -1,5 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.git
|
|
||||||
node_modules
|
|
||||||
.eslintignore
|
|
||||||
.gitignore
|
|
||||||
LICENSE
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"semi": false
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Use an official Node.js image as the base
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Set environment variable to skip Chromium download
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
|
||||||
|
|
||||||
# Install git and Python
|
|
||||||
RUN apk update && apk add --no-cache git python3 make g++
|
|
||||||
|
|
||||||
# Create a symlink for python3 as python
|
|
||||||
RUN ln -sf /usr/bin/python3 /usr/bin/python
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json to the container
|
|
||||||
# COPY package*.json ./
|
|
||||||
COPY /src/Managing.WebApp/package.json ./
|
|
||||||
|
|
||||||
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
|
|
||||||
RUN npm install --legacy-peer-deps
|
|
||||||
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
|
|
||||||
|
|
||||||
# Copy the rest of the app's source code to the container
|
|
||||||
# COPY . .
|
|
||||||
COPY src/Managing.WebApp/ /app/
|
|
||||||
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
|
|
||||||
|
|
||||||
# Build the app
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Use NGINX as the web server
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
# Copy the built app to the NGINX web server directory
|
|
||||||
COPY --from=0 /app/build /usr/share/nginx/html
|
|
||||||
|
|
||||||
# Expose port 80 for the NGINX web server
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# Start the NGINX web server
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Use an official Node.js image as the base
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Set environment variable to skip Chromium download
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
|
||||||
|
|
||||||
# Install git and Python
|
|
||||||
RUN apk update && apk add --no-cache git python3 make g++
|
|
||||||
|
|
||||||
# Create a symlink for python3 as python
|
|
||||||
RUN ln -sf /usr/bin/python3 /usr/bin/python
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json to the container
|
|
||||||
# COPY package*.json ./
|
|
||||||
COPY /src/Managing.Pinky/package.json ./
|
|
||||||
|
|
||||||
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
|
|
||||||
RUN npm install --legacy-peer-deps
|
|
||||||
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
|
|
||||||
|
|
||||||
# Copy the rest of the app's source code to the container
|
|
||||||
# COPY . .
|
|
||||||
RUN ls -la
|
|
||||||
COPY src/Managing.Pinky/ .
|
|
||||||
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
|
|
||||||
|
|
||||||
# Build the app
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Use NGINX as the web server
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
# Copy the built app to the NGINX web server directory
|
|
||||||
# COPY --from=0 /app/build /usr/share/nginx/html
|
|
||||||
COPY --from=0 /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
# Expose port 80 for the NGINX web server
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# Start the NGINX web server
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) Managing
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# vite-react-ts-extended [](https://github.com/laststance/vite-react-ts-extended/actions/workflows/typecheck.yml) [](https://github.com/laststance/vite-react-ts-extended/actions/workflows/test.yml) [](https://github.com/laststance/vite-react-ts-extended/actions/workflows/build.yml) [](https://github.com/laststance/vite-react-ts-extended/actions/workflows/lint.yml) [](https://depfu.com/github/laststance/vite-react-ts-extended?project_id=32682)
|
|
||||||
|
|
||||||
> My CRA alternative.
|
|
||||||
> Create plain and lightweight React+TS programming environment with familiar pre-setup tooling
|
|
||||||
> eslint/prettier, jest/TS/react-testing-library/msw, tailwindcss, CI.
|
|
||||||
|
|
||||||
## [Trying this Online!](https://codesandbox.io/s/vite-react-ts-extended-cbgyfz?file=/src/App.tsx)
|
|
||||||
|
|
||||||
<img src="https://digital3.nyc3.cdn.digitaloceanspaces.com/ext.png" />
|
|
||||||
|
|
||||||
This is the official [Vite](https://vitejs.dev/) template(`npm init vite@latest myapp -- --template react-ts`) and some extended setup.
|
|
||||||
|
|
||||||
- [eslint-typescript](https://github.com/typescript-eslint/typescript-eslint) and [Prettier](https://prettier.io/) integration. Rules are 100% my personal setup 💅
|
|
||||||
- [jest](https://jestjs.io/), [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/), [react-hooks-testing-library](https://github.com/testing-library/react-hooks-testing-library), [MSW](https://mswjs.io/)
|
|
||||||
- [tailwindcss](https://tailwindcss.com/)
|
|
||||||
- [Github Actions](https://github.com/features/actions)
|
|
||||||
|
|
||||||
All npm package are keeping least release version powered by [Depfu](https://depfu.com/).
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
npx degit laststance/vite-react-ts-extended myapp
|
|
||||||
```
|
|
||||||
|
|
||||||
### yarn
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd myapp
|
|
||||||
yarn install
|
|
||||||
yarn validate # The installation was successful if no error occurs after running 'validate'.
|
|
||||||
yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### npm
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd myapp
|
|
||||||
npm install
|
|
||||||
npm run validate # The installation was successful if no error occurs after running 'validate'.
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Commands
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn dev # start development server
|
|
||||||
yarn validate # run test,lint,build,typecheck concurrently
|
|
||||||
yarn test # run jest
|
|
||||||
yarn lint # run eslint
|
|
||||||
yarn lint:fix # run eslint with --fix option
|
|
||||||
yarn typecheck # run TypeScript compiler check
|
|
||||||
yarn build # build production bundle to 'dist' directly
|
|
||||||
yarn prettier # run prettier for json|yml|css|md|mdx files
|
|
||||||
yarn clean # remove 'node_modules' 'yarn.lock' 'dist' completely
|
|
||||||
yarn serve # launch server for production bundle in local
|
|
||||||
```
|
|
||||||
|
|
||||||
# Background
|
|
||||||
|
|
||||||
The evolution of the React framework is accelerating more than ever before.
|
|
||||||
[Next.js](https://nextjs.org/), [Remix](https://remix.run/), [RedwoodJS](https://redwoodjs.com/), [Gatsby](https://www.gatsbyjs.com/), [Blitz](https://blitzjs.com/) etc...
|
|
||||||
|
|
||||||
Ahthough I still need plain React programming starter some reason. (.e.g Demo, Experiment like Deep Dive React Core.)
|
|
||||||
So far, [create-react-app](https://github.com/facebook/create-react-app) **was** it.
|
|
||||||
In short, [create-react-app](https://github.com/facebook/create-react-app) development couldn't say active. Please read the [Issue](https://github.com/facebook/create-react-app/issues/11180) in details.
|
|
||||||
|
|
||||||
So I created an alternative to [create-react-app](https://github.com/facebook/create-react-app) for myself, based on [Vite](https://github.com/facebook/create-react-app).
|
|
||||||
This project contains my very opinionted setup,
|
|
||||||
but I hope it will be a useful tool for people who have similar needs to mine! 😀
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
## Contributors ✨
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center"><a href="http://ryota-murakami.github.io/"><img src="https://avatars1.githubusercontent.com/u/5501268?s=400&u=7bf6b1580b95930980af2588ef0057f3e9ec1ff8&v=4?s=100" width="100px;" alt=""/><br /><sub><b>ryota-murakami</b></sub></a><br /><a href="https://github.com/laststance/vite-react-ts-extended/laststance/vite-react-ts-extended/commits?author=ryota-murakami" title="Code">💻</a> <a href="https://github.com/laststance/vite-react-ts-extended/laststance/vite-react-ts-extended/commits?author=ryota-murakami" title="Documentation">📖</a> <a href="https://github.com/laststance/vite-react-ts-extended/laststance/vite-react-ts-extended/commits?author=ryota-murakami" title="Tests">⚠️</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
@@ -1,493 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
||||||
<title>Happy Birthday My Love</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<style>
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Press Start 2P', cursive;
|
|
||||||
background-color: #f5e6ff;
|
|
||||||
color: #5a3d7a;
|
|
||||||
overflow-x: hidden;
|
|
||||||
touch-action: manipulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-box {
|
|
||||||
border: 4px solid #5a3d7a;
|
|
||||||
box-shadow: 8px 8px 0 rgba(90, 61, 122, 0.2);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-box::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
right: 2px;
|
|
||||||
bottom: 2px;
|
|
||||||
border: 2px solid #d9b3ff;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-btn {
|
|
||||||
border: 3px solid #5a3d7a;
|
|
||||||
box-shadow: 4px 4px 0 rgba(90, 61, 122, 0.2);
|
|
||||||
transition: all 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-btn:active {
|
|
||||||
transform: translate(2px, 2px);
|
|
||||||
box-shadow: 2px 2px 0 rgba(90, 61, 122, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-divider {
|
|
||||||
height: 4px;
|
|
||||||
background-color: #5a3d7a;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-divider::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 2px;
|
|
||||||
background-color: #d9b3ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-avatar {
|
|
||||||
border: 3px solid #5a3d7a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-text {
|
|
||||||
text-shadow: 2px 2px 0 rgba(90, 61, 122, 0.2);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-scene {
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide audio controls */
|
|
||||||
#bgMusic {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pixel heart animation */
|
|
||||||
@keyframes pulse {
|
|
||||||
0% { transform: scale(1); }
|
|
||||||
50% { transform: scale(1.1); }
|
|
||||||
100% { transform: scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.pixel-heart {
|
|
||||||
animation: pulse 1.5s infinite;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Password modal styles */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
text-align: center;
|
|
||||||
animation: fadeIn 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: scale(0.8); }
|
|
||||||
to { opacity: 1; transform: scale(1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shake {
|
|
||||||
animation: shake 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shake {
|
|
||||||
0%, 100% { transform: translateX(0); }
|
|
||||||
25% { transform: translateX(-10px); }
|
|
||||||
75% { transform: translateX(10px); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="relative">
|
|
||||||
<!-- Password Modal -->
|
|
||||||
<div id="passwordModal" class="modal-overlay">
|
|
||||||
<div class="modal-content pixel-box">
|
|
||||||
<h2 class="text-xl mb-6 text-purple-800 pixel-text">Enter Password</h2>
|
|
||||||
<div class="mb-4">
|
|
||||||
<input type="password" id="passwordInput"
|
|
||||||
class="w-full px-4 py-2 border-2 border-purple-300 rounded focus:outline-none focus:border-purple-500"
|
|
||||||
placeholder="Password">
|
|
||||||
</div>
|
|
||||||
<button id="submitPassword"
|
|
||||||
class="pixel-btn bg-purple-500 text-white px-6 py-2 rounded hover:bg-purple-600 transition-colors">
|
|
||||||
Enter
|
|
||||||
</button>
|
|
||||||
<p id="errorMessage" class="mt-4 text-red-500 text-sm hidden pixel-text">Incorrect password. Try again!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div id="mainContent" class="content-hidden">
|
|
||||||
<!-- Background Music -->
|
|
||||||
<audio id="bgMusic" loop>
|
|
||||||
<source src="sound.mp3" type="audio/mpeg" autoplay>
|
|
||||||
</audio>
|
|
||||||
|
|
||||||
<!-- Music Toggle Button -->
|
|
||||||
<button id="musicToggle" class="fixed bottom-4 right-4 z-50 w-12 h-12 bg-purple-300 pixel-btn rounded-full flex items-center justify-center">
|
|
||||||
<svg id="musicIcon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#5a3d7a">
|
|
||||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Navigation Dots -->
|
|
||||||
<div class="fixed right-4 top-1/2 transform -translate-y-1/2 z-40 flex flex-col space-y-3">
|
|
||||||
<a href="#section1" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section2" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section3" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section4" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section5" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section6" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section7" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section8" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
<a href="#section9" class="w-3 h-3 rounded-full bg-purple-300 border-2 border-purple-700 dot"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sections -->
|
|
||||||
<div id="section1" class="section bg-pink-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://img.freepik.com/premium-photo/high-rise-buildings-downtown-bangkok-night_5219-2322.jpg?w=996" alt="Rooftop pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h1 class="text-xl md:text-2xl text-center mb-6 text-purple-800 pixel-text">Happy Birthday My Love!</h1>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Our journey together began on a rooftop...</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">Where we first met under the stars ✨</p>
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-10 h-10 pixel-heart">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section2" class="section bg-purple-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQcLaTSzF2i_kDkosNQgoW8bgqg4rq-568TXw&s" alt="Park pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Our First Date</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Walking in the park together</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">I knew then you were special 💕</p>
|
|
||||||
<div class="flex justify-between mt-8">
|
|
||||||
<img src="pics/khaosok.JPG" alt="You" class="w-16 h-16 pixel-avatar">
|
|
||||||
<img src="pics/temple.jpg" alt="Her" class="w-16 h-16 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section3" class="section bg-pink-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="pics/cook.jpg" alt="Kitchen pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Our First Cooking Night</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Making dinner together in your kitchen</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">Simple moments that mean everything 🍳</p>
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-10 h-10 pixel-heart">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section4" class="section bg-purple-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://www.airportels.asia/wp-content/uploads/2023/12/Shopping-Spots-in-Chinatown-scaled.webp" alt="Chinese restaurant pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Chinese Restaurant</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">"I like to try things"</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">And trying life with you has been the best adventure 🥢</p>
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-10 h-10 pixel-heart">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section5" class="section bg-pink-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://media-cdn.tripadvisor.com/media/attractions-splice-spp-674x446/07/00/a4/db.jpg" alt="Kao Sok pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Kao Sok Adventure</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Our trip to Kao Sok with my best friend</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">Sharing my world with you 🌿</p>
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel jungle" class="w-24 h-24">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section6" class="section bg-purple-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://dynamic-media-cdn.tripadvisor.com/media/photo-o/07/b9/58/b4/photo0jpg.jpg?w=1200&h=-1&s=1&cx=1000&cy=543&chk=v1_d6044045ef865bfe074a" alt="Kuala Lumpur pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Kuala Lumpur</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Joining you during your work trip</p>
|
|
||||||
<p class="text-xs md:text-sm pixel-text">Because every moment with you is precious 🌆</p>
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-10 h-10 pixel-heart">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section7" class="section bg-pink-100">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://img.static-af.com/transform/45cb9a13-b167-4842-8ea8-05d0cc7a4d04/" alt="France pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Time Apart</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Me leaving for France</p>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">You facing work difficulties</p>
|
|
||||||
<p class="text-xs md:text-sm text-purple-600 pixel-text">Remember to take time for yourself</p>
|
|
||||||
<p class="text-xs md:text-sm text-purple-600 pixel-text">Your health and happiness matter most 💖</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="section8" class="section bg-gradient-to-b from-purple-200 to-pink-200">
|
|
||||||
<div class="pixel-box bg-white p-6 mx-auto max-w-md">
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="pics/boat.jpg" alt="Birthday kiss pixel art" class="w-64 h-64 pixel-avatar">
|
|
||||||
</div>
|
|
||||||
<h2 class="text-lg md:text-xl text-center mb-6 text-purple-800 pixel-text">Happy Birthday!</h2>
|
|
||||||
<p class="text-xs md:text-sm mb-4 pixel-text">Back together in Bangkok</p>
|
|
||||||
<p class="text-xs md:text-sm mb-6 pixel-text">Celebrating you on your special day 🎂</p>
|
|
||||||
<div class="flex justify-center mb-6">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-16 h-16 pixel-heart">
|
|
||||||
</div>
|
|
||||||
<p class="text-xs md:text-sm text-center pixel-text">I love you more than words can say</p>
|
|
||||||
<p class="text-xs md:text-sm text-center text-purple-600 pixel-text mt-4">(Tap the heart below)</p>
|
|
||||||
<div class="flex justify-center mt-6">
|
|
||||||
<button id="finalHeart" class="pixel-btn bg-pink-200 p-4 rounded-full">
|
|
||||||
<img src="https://cdn.pixabay.com/photo/2017/09/23/16/33/pixel-heart-2779422_1280.png" alt="Pixel heart" class="w-12 h-12 pixel-heart">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="kissAnimation" class="hidden mt-6 flex justify-center">
|
|
||||||
<img src="https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/e7781e25-3bf5-4185-aad7-ec93d0b5e1b0/d7fa2wq-cf60174b-9a41-44de-bb2f-605cde85dad5.gif?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcL2U3NzgxZTI1LTNiZjUtNDE4NS1hYWQ3LWVjOTNkMGI1ZTFiMFwvZDdmYTJ3cS1jZjYwMTc0Yi05YTQxLTQ0ZGUtYmIyZi02MDVjZGU4NWRhZDUuZ2lmIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.vX_oTdMeJ4D5xWpqNQV4NOjiJW4ahpQWZ4Fyp4xX8x8" alt="Pixel kiss" class="w-32 h-32">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Music toggle functionality
|
|
||||||
const musicToggle = document.getElementById('musicToggle');
|
|
||||||
const musicIcon = document.getElementById('musicIcon');
|
|
||||||
const bgMusic = document.getElementById('bgMusic');
|
|
||||||
let musicPlaying = true;
|
|
||||||
|
|
||||||
// Attempt to autoplay music (may be blocked by browser)
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const playPromise = bgMusic.play();
|
|
||||||
|
|
||||||
if (playPromise !== undefined) {
|
|
||||||
playPromise.then(_ => {
|
|
||||||
musicPlaying = true;
|
|
||||||
musicIcon.innerHTML = '<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
musicPlaying = false;
|
|
||||||
musicIcon.innerHTML = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
musicToggle.addEventListener('click', function() {
|
|
||||||
if (musicPlaying) {
|
|
||||||
bgMusic.pause();
|
|
||||||
musicIcon.innerHTML = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
|
||||||
musicPlaying = false;
|
|
||||||
} else {
|
|
||||||
bgMusic.play();
|
|
||||||
musicIcon.innerHTML = '<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>';
|
|
||||||
musicPlaying = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Final heart click animation
|
|
||||||
const finalHeart = document.getElementById('finalHeart');
|
|
||||||
const kissAnimation = document.getElementById('kissAnimation');
|
|
||||||
|
|
||||||
finalHeart.addEventListener('click', function() {
|
|
||||||
kissAnimation.classList.remove('hidden');
|
|
||||||
finalHeart.classList.add('hidden');
|
|
||||||
|
|
||||||
// Create floating hearts
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
createFloatingHeart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function createFloatingHeart() {
|
|
||||||
const heart = document.createElement('div');
|
|
||||||
heart.innerHTML = '❤️';
|
|
||||||
heart.className = 'absolute text-xl animate-float';
|
|
||||||
heart.style.left = Math.random() * 100 + 'vw';
|
|
||||||
heart.style.top = '100vh';
|
|
||||||
heart.style.animationDuration = (Math.random() * 3 + 2) + 's';
|
|
||||||
document.body.appendChild(heart);
|
|
||||||
|
|
||||||
// Remove heart after animation
|
|
||||||
setTimeout(() => {
|
|
||||||
heart.remove();
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add animation style
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.innerHTML = `
|
|
||||||
@keyframes float {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0) rotate(0deg);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-100vh) rotate(360deg);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animate-float {
|
|
||||||
animation: float linear forwards;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
// Update navigation dots based on scroll position
|
|
||||||
const dots = document.querySelectorAll('.dot');
|
|
||||||
const sections = document.querySelectorAll('.section');
|
|
||||||
|
|
||||||
function updateActiveDot() {
|
|
||||||
const scrollPosition = window.scrollY;
|
|
||||||
|
|
||||||
sections.forEach((section, index) => {
|
|
||||||
const sectionTop = section.offsetTop;
|
|
||||||
const sectionHeight = section.offsetHeight;
|
|
||||||
|
|
||||||
if (scrollPosition >= sectionTop - 50 && scrollPosition < sectionTop + sectionHeight - 50) {
|
|
||||||
dots.forEach(dot => dot.classList.remove('bg-purple-700'));
|
|
||||||
dots[index].classList.add('bg-purple-700');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', updateActiveDot);
|
|
||||||
updateActiveDot(); // Initialize
|
|
||||||
|
|
||||||
// Password protection
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const passwordModal = document.getElementById('passwordModal');
|
|
||||||
const mainContent = document.getElementById('mainContent');
|
|
||||||
const passwordInput = document.getElementById('passwordInput');
|
|
||||||
const submitButton = document.getElementById('submitPassword');
|
|
||||||
const errorMessage = document.getElementById('errorMessage');
|
|
||||||
const correctPassword = 'iloveyou';
|
|
||||||
const bgMusic = document.getElementById('bgMusic');
|
|
||||||
const musicToggle = document.getElementById('musicToggle');
|
|
||||||
const musicIcon = document.getElementById('musicIcon');
|
|
||||||
|
|
||||||
// Check if already authenticated
|
|
||||||
if (sessionStorage.getItem('authenticated') === 'true') {
|
|
||||||
passwordModal.style.display = 'none';
|
|
||||||
mainContent.classList.remove('content-hidden');
|
|
||||||
// Start playing music
|
|
||||||
bgMusic.play().catch(error => {
|
|
||||||
console.log('Auto-play prevented by browser');
|
|
||||||
musicPlaying = false;
|
|
||||||
updateMusicIcon(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPassword() {
|
|
||||||
if (passwordInput.value === correctPassword) {
|
|
||||||
passwordModal.style.display = 'none';
|
|
||||||
mainContent.classList.remove('content-hidden');
|
|
||||||
sessionStorage.setItem('authenticated', 'true');
|
|
||||||
|
|
||||||
// Start playing music after password entry
|
|
||||||
bgMusic.play().catch(error => {
|
|
||||||
console.log('Auto-play prevented by browser');
|
|
||||||
musicPlaying = false;
|
|
||||||
updateMusicIcon(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
errorMessage.classList.remove('hidden');
|
|
||||||
passwordInput.value = '';
|
|
||||||
passwordModal.querySelector('.modal-content').classList.add('shake');
|
|
||||||
setTimeout(() => {
|
|
||||||
passwordModal.querySelector('.modal-content').classList.remove('shake');
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMusicIcon(playing) {
|
|
||||||
if (playing) {
|
|
||||||
musicIcon.innerHTML = '<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>';
|
|
||||||
} else {
|
|
||||||
musicIcon.innerHTML = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Music toggle functionality
|
|
||||||
musicToggle.addEventListener('click', function() {
|
|
||||||
if (musicPlaying) {
|
|
||||||
bgMusic.pause();
|
|
||||||
musicPlaying = false;
|
|
||||||
} else {
|
|
||||||
bgMusic.play();
|
|
||||||
musicPlaying = true;
|
|
||||||
}
|
|
||||||
updateMusicIcon(musicPlaying);
|
|
||||||
});
|
|
||||||
|
|
||||||
submitButton.addEventListener('click', checkPassword);
|
|
||||||
passwordInput.addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
checkPassword();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Array of known potentially hanging packages
|
|
||||||
hanging_packages=("xmlhttprequest-ssl@latest" "engine.io-parser@latest")
|
|
||||||
|
|
||||||
# Timeout in seconds for each package installation attempt
|
|
||||||
|
|
||||||
|
|
||||||
install_with_timeout() {
|
|
||||||
package=$1
|
|
||||||
echo "Attempting to install $package with a timeout of $timeout_duration seconds."
|
|
||||||
# Start npm install in the background
|
|
||||||
npm install $package --verbose &> $package.log &
|
|
||||||
|
|
||||||
# Get PID of the npm process
|
|
||||||
pid=$!
|
|
||||||
|
|
||||||
# Wait for the npm process to finish or timeout
|
|
||||||
(sleep $timeout_duration && kill -0 $pid 2>/dev/null && kill -9 $pid && echo "Timeout reached for $package, process killed." && echo $package >> exclude.log) &
|
|
||||||
waiter_pid=$!
|
|
||||||
|
|
||||||
# Wait for the npm process to complete
|
|
||||||
wait $pid
|
|
||||||
|
|
||||||
# Kill the waiter process in case npm finished before the timeout
|
|
||||||
kill -0 $waiter_pid 2>/dev/null && kill -9 $waiter_pid
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install potentially hanging packages first with a timeout
|
|
||||||
for package in "${hanging_packages[@]}"; do
|
|
||||||
install_with_timeout $package
|
|
||||||
done
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
const config = {
|
|
||||||
collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
|
|
||||||
moduleDirectories: ['node_modules'],
|
|
||||||
moduleFileExtensions: ['js', 'mjs', 'jsx', 'ts', 'tsx', 'json'],
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
|
|
||||||
},
|
|
||||||
notify: true,
|
|
||||||
notifyMode: 'success-change',
|
|
||||||
resetMocks: true,
|
|
||||||
roots: ['<rootDir>'],
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/jest/setupTests.ts'],
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
testMatch: [
|
|
||||||
'<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
|
|
||||||
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
|
|
||||||
],
|
|
||||||
transform: {
|
|
||||||
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)':
|
|
||||||
'<rootDir>/jest/fileTransform.js',
|
|
||||||
'^.+\\.[jt]sx?$': 'esbuild-jest',
|
|
||||||
'^.+\\.css$': '<rootDir>/jest/cssTransform.js',
|
|
||||||
},
|
|
||||||
transformIgnorePatterns: [
|
|
||||||
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
|
|
||||||
'^.+\\.module\\.(css|sass|scss)$',
|
|
||||||
],
|
|
||||||
verbose: true,
|
|
||||||
watchPlugins: [
|
|
||||||
'jest-watch-typeahead/filename',
|
|
||||||
'jest-watch-typeahead/testname',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = config
|
|
||||||
3400
src/Managing.Pinky/package-lock.json
generated
3400
src/Managing.Pinky/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "managing",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"serve": "serve -s dist -p 3000",
|
|
||||||
"test": "jest",
|
|
||||||
"lint": "eslint . --ext .ts,.tsx,.js,jsx",
|
|
||||||
"lint:fix": "eslint . --ext .ts,.tsx,.js,jsx --fix",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"prettier": "prettier --write \"**/*.+(json|yml|css|md|mdx)\"",
|
|
||||||
"clean": "rimraf node_modules yarn.lock dist",
|
|
||||||
"validate": "./scripts/validate"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@heroicons/react": "^1.0.6",
|
|
||||||
"@tailwindcss/typography": "^0.5.0",
|
|
||||||
"@tanstack/react-query": "^5.67.1",
|
|
||||||
"autoprefixer": "^10.4.7",
|
|
||||||
"classnames": "^2.3.1",
|
|
||||||
"jotai": "^1.6.7",
|
|
||||||
"latest-version": "^9.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/elliptic": "^6.4.18",
|
|
||||||
"@types/react": "^18.0.9",
|
|
||||||
"@types/react-dom": "^18.0.4",
|
|
||||||
"@types/react-grid-layout": "^1.3.2",
|
|
||||||
"@types/react-plotly.js": "^2.6.0",
|
|
||||||
"@types/react-slider": "^1.3.1",
|
|
||||||
"@types/react-table": "^7.7.12",
|
|
||||||
"@vitejs/plugin-react": "^1.3.2",
|
|
||||||
"autoprefixer": "^10.4.7",
|
|
||||||
"daisyui": "^3.5.1",
|
|
||||||
"postcss": "^8.4.13",
|
|
||||||
"prettier": "^2.6.1",
|
|
||||||
"prettier-plugin-tailwind-css": "^1.5.0",
|
|
||||||
"tailwindcss": "^3.0.23",
|
|
||||||
"typescript": "^5.7.3",
|
|
||||||
"vite": "^6.0.11"
|
|
||||||
},
|
|
||||||
"msw": {
|
|
||||||
"workerDirectory": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 475 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 850 KiB |
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
autoprefixer: {},
|
|
||||||
tailwindcss: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: [require('prettier-plugin-tailwindcss')],
|
|
||||||
tailwindConfig: './tailwind.config.js',
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,12 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
||||||
daisyui: {
|
|
||||||
themes: ['black', 'coffee', 'cyberpunk', 'lofi', 'retro', 'kaigen'],
|
|
||||||
},
|
|
||||||
plugins: [require('@tailwindcss/typography'), require('daisyui')],
|
|
||||||
theme: {
|
|
||||||
container: {
|
|
||||||
center: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx"
|
|
||||||
},
|
|
||||||
"include": ["./src", "hardhat.config.js"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import react from '@vitejs/plugin-react'
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
build: {
|
|
||||||
minify: false,
|
|
||||||
sourcemap: false,
|
|
||||||
target: 'es2022',
|
|
||||||
},
|
|
||||||
optimizeDeps: {
|
|
||||||
esbuildOptions: {
|
|
||||||
target: 'es2022',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [react()],
|
|
||||||
publicDir: 'assets',
|
|
||||||
server: {
|
|
||||||
host: true,
|
|
||||||
open: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user