Postgres (#30)

* Add postgres

* Migrate users

* Migrate geneticRequest

* Try to fix Concurrent call

* Fix asyncawait

* Fix async and concurrent

* Migrate backtests

* Add cache for user by address

* Fix backtest migration

* Fix not open connection

* Fix backtest command error

* Fix concurrent

* Fix all concurrency

* Migrate TradingRepo

* Fix scenarios

* Migrate statistic repo

* Save botbackup

* Add settings et moneymanagement

* Add bot postgres

* fix a bit more backups

* Fix bot model

* Fix loading backup

* Remove cache market for read positions

* Add workers to postgre

* Fix workers api

* Reduce get Accounts for workers

* Migrate synth to postgre

* Fix backtest saved

* Remove mongodb

* botservice decorrelation

* Fix tradingbot scope call

* fix tradingbot

* fix concurrent

* Fix scope for genetics

* Fix account over requesting

* Fix bundle backtest worker

* fix a lot of things

* fix tab backtest

* Remove optimized moneymanagement

* Add light signal to not use User and too much property

* Make money management lighter

* insert indicators to awaitable

* Migrate add strategies to await

* Refactor scenario and indicator retrieval to use asynchronous methods throughout the application

* add more async await

* Add services

* Fix and clean

* Fix bot a bit

* Fix bot and add message for cooldown

* Remove fees

* Add script to deploy db

* Update dfeeploy script

* fix script

* Add idempotent script and backup

* finish script migration

* Fix did user and agent name on start bot
This commit is contained in:
Oda
2025-07-27 15:42:17 +02:00
committed by GitHub
parent 361bfbf6e8
commit 422fecea7b
294 changed files with 23953 additions and 7272 deletions

View File

@@ -3,7 +3,7 @@
## Requirements ## Requirements
- NET .Core framework - NET .Core framework
- MongoDb - PostgreSQL database
- Node.JS - Node.JS
- Discord server with API keys - Discord server with API keys
- Alchemy Keys - Alchemy Keys

View File

@@ -653,3 +653,23 @@ npm run prepare-code
For more details, see the [scripts documentation](scripts/README.md). For more details, see the [scripts documentation](scripts/README.md).
# Entity Framework Core Migrations
To manage database schema changes for the backend, use the following EF Core commands from the project root:
## Add a Migration
```
dotnet ef migrations add <MigrationName> --project src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj --context ManagingDbContext
```
## Update the Database
```
dotnet ef database update --project src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj --context ManagingDbContext
```
- Replace `<MigrationName>` with your desired migration name.
- These commands will build the project and apply migrations to the configured database.
- If you drop a table or reset the schema, you may need to remove old migrations and create a new one.

View File

@@ -1,191 +1,63 @@
# Scripts # 🚀 Safe Database Migration Script
This directory contains utility scripts for the project. A clean, focused script for safe database migrations with automatic backup and cleanup.
## Add JS Extensions Script ## 📋 Features
The `add-js-extensions.mjs` script adds `.js` extensions to import statements in JavaScript and TypeScript files, and also adds the required JSON import assertions. -**Connectivity Check**: Verifies database connection before proceeding
-**Automatic Backup**: Creates backup with retry logic (3 attempts)
-**Safety First**: Exits if backup fails - no migration without backup
-**Smart PostgreSQL**: Uses Docker PostgreSQL client if `psql` not available
-**Migration**: Runs Entity Framework migrations
-**Verification**: Checks migration success
-**Cleanup**: Keeps only last 5 backups per environment
### Why This Script? ## 🛠️ Usage
When working with ES Modules in Node.js:
1. Imports of local files require explicit file extensions
2. JSON imports require an `assert { type: 'json' }` assertion
This script automates both processes to ensure your code is ESM-compatible.
### Usage
Run the script with npm:
```bash ```bash
# Fix imports in the src directory (default) # Development environment
npm run fix-imports ./safe-migrate.sh Development
# Fix imports in a specific directory # Sandbox environment
npm run fix-imports-dir -- path/to/directory ./safe-migrate.sh Sandbox
# Production environment
./safe-migrate.sh Production
``` ```
Or run the script directly: ## 🔄 Process Flow
```bash 1. **Environment Validation**: Validates environment parameter
# Fix imports in the src directory (default) 2. **Connectivity Check**: Tests database connection
node scripts/add-js-extensions.mjs 3. **Backup Creation**: Creates backup with retry logic
4. **Migration**: Runs pending migrations
5. **Verification**: Checks migration success
6. **Cleanup**: Removes old backups (keeps last 5)
# Fix imports in a specific directory ## 🚨 Safety Features
node scripts/add-js-extensions.mjs path/to/directory
- **Mandatory Backup**: Script exits if backup cannot be created
- **Retry Logic**: 3 backup attempts with 5-second delays
- **Error Handling**: Clear error messages and exit codes
- **Logging**: Detailed logs for troubleshooting
## 📊 Output
```
✅ Database connectivity test passed
✅ Database backup created: ./backups/managing_Development_backup_20250726_043047.sql
✅ Database migration completed successfully
✅ Database schema verification passed
✅ Kept last 5 backups for Development environment
``` ```
### What This Script Does ## 🔧 Prerequisites
1. Recursively scans all JavaScript and TypeScript files in the specified directory - Docker (for PostgreSQL client)
2. Identifies import statements with relative paths (starting with `./` or `../`) that don't have extensions and adds `.js` extensions - .NET 8.0 SDK
3. Identifies JSON imports that are missing the required assertion and adds `assert { type: 'json' }` - Access to target database
4. Provides a summary of files modified and any errors encountered
### Examples ## 📁 Files
Before: - **Backups**: `./backups/managing_[Environment]_backup_[Timestamp].sql`
```javascript - **Logs**: `./migration_[Environment]_[Timestamp].log`
import { bigMath } from "./bigmath";
import data from "./data.json";
```
After:
```javascript
import { bigMath } from "./bigmath.js";
import data from "./data.json" assert { type: 'json' };
```
### Important Notes
- The script only modifies imports with relative paths (starting with `./` or `../`)
- It skips imports that already have a file extension (except for JSON files)
- It adds `.js` extensions to extensionless imports
- It adds `assert { type: 'json' }` to JSON imports that don't already have it
- It handles regular imports, dynamic imports, and exports
## Remove JSON Assertions Script
The `remove-json-assertions.mjs` script removes `assert { type: 'json' }` assertions from JSON imports.
### Why This Script?
Different JavaScript environments have different requirements for JSON imports:
1. Some newer environments require the `assert { type: 'json' }` assertion
2. Others don't support or need these assertions
This script removes these assertions to improve compatibility with environments that don't need them.
### Usage
Run the script with npm:
```bash
# Remove JSON assertions
npm run remove-json-assertions
# Run both import fixes and assertion removal in one command
npm run prepare-code
```
Or run the script directly:
```bash
node scripts/remove-json-assertions.mjs
```
### What This Script Does
1. Recursively scans all JavaScript and TypeScript files in the project
2. Identifies JSON imports with `assert { type: 'json' }` assertions
3. Removes the assertions while preserving the import statements
4. Provides a summary of files modified
### Examples
Before:
```javascript
import data from "./data.json" assert { type: 'json' };
```
After:
```javascript
import data from "./data.json";
```
### Important Notes
- The script only modifies JSON imports with assertions
- It preserves all other import statements
- It works in conjunction with the add-js-extensions script
- These scripts can be run in sequence to first fix imports then remove assertions
## Update JSON Imports Script
The `update-json-imports.mjs` script updates JSON imports to use the modern `with { type: "json" }` syntax.
### Why This Script?
Different JavaScript environments have different requirements for JSON imports:
1. Older environments used `assert { type: 'json' }` for JSON imports
2. Modern JavaScript environments now use the `with { type: "json" }` syntax
This script updates your codebase to use the newer, more standardized approach.
### Usage
Run the script with npm:
```bash
# Update JSON import syntax
npm run update-json-imports
# Run both import fixing and JSON import updating in one command
npm run prepare-code
```
Or run the script directly:
```bash
node scripts/update-json-imports.mjs
```
### What This Script Does
1. Recursively scans all JavaScript and TypeScript files in the project
2. Identifies JSON imports using either:
- The older `assert { type: 'json' }` syntax
- No type assertion at all
- Erroneous dual syntax (`assert { type: 'json' } with { type: "json" }`)
3. Updates them to use the modern `with { type: "json" }` syntax
4. Provides a summary of files modified
### Examples
Before (old assert syntax):
```javascript
import data from "./data.json" assert { type: 'json' };
```
Before (no type assertion):
```javascript
import data from "./data.json";
```
Before (erroneous dual syntax):
```javascript
import data from "./data.json" assert { type: 'json' } with { type: "json" };
```
After (in all cases):
```javascript
import data from "./data.json" with { type: "json" };
```
### Important Notes
- The script updates all JSON imports to use the modern syntax
- It properly fixes cases where both old and new syntax are present
- It preserves all other import statements
- Files with properly formatted imports are not modified

617
scripts/safe-migrate.sh Executable file
View File

@@ -0,0 +1,617 @@
#!/bin/bash
# Safe Database Migration Script
# Usage: ./safe-migrate.sh [environment]
# Environments: Development, Sandbox, Production, Oda
set -e # Exit on any error
ENVIRONMENT=${1:-"Development"} # Default to Development for safer initial testing
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR_NAME="backups" # Just the directory name
LOGS_DIR_NAME="logs" # Just the directory name
# Get the directory where the script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Create logs directory first (before LOG_FILE is used)
LOGS_DIR="$SCRIPT_DIR/$LOGS_DIR_NAME"
mkdir -p "$LOGS_DIR" || { echo "Failed to create logs directory: $LOGS_DIR"; exit 1; }
LOG_FILE="./logs/migration_${ENVIRONMENT}_${TIMESTAMP}.log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE"
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" | tee -a "$LOG_FILE"
exit 1
}
info() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}" | tee -a "$LOG_FILE"
}
# --- Determine Base Paths ---
# Get the directory where the script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
log "Script is located in: $SCRIPT_DIR"
# Define absolute paths for projects and common directories relative to the script
# Assuming the project structure is:
# your_repo/
# ├── scripts/safe-migrate.sh
# └── src/
# ├── Managing.Api/
# ├── Managing.Infrastructure.Database/
# └── Managing.Docker/
PROJECT_ROOT_DIR="$(dirname "$SCRIPT_DIR")" # One level up from scripts/
SRC_DIR="$PROJECT_ROOT_DIR/src"
DB_PROJECT_PATH="$SRC_DIR/Managing.Infrastructure.Database"
API_PROJECT_PATH="$SRC_DIR/Managing.Api"
DOCKER_DIR="$SRC_DIR/Managing.Docker" # Adjust if your docker-compose files are elsewhere
# Define absolute path for backup directory with environment subfolder
BACKUP_DIR="$SCRIPT_DIR/$BACKUP_DIR_NAME/$ENVIRONMENT"
# --- Pre-checks and Setup ---
info "Pre-flight checks..."
command -v dotnet >/dev/null 2>&1 || error ".NET SDK is not installed. Please install .NET SDK to run this script."
command -v docker >/dev/null 2>&1 || warn "Docker is not installed. This is fine if not running Development or Oda environment with Docker."
command -v psql >/dev/null 2>&1 || warn "PostgreSQL CLI (psql) is not installed. Database connectivity checks will be skipped."
command -v pg_dump >/dev/null 2>&1 || warn "PostgreSQL pg_dump is not installed. Will use EF Core migration script for backup instead."
# Create backup directory (with environment subfolder)
mkdir -p "$BACKUP_DIR" || error "Failed to create backup directory: $BACKUP_DIR"
log "Backup directory created/verified: $BACKUP_DIR"
log "🚀 Starting safe migration for environment: $ENVIRONMENT"
# Validate environment
case $ENVIRONMENT in
"Development"|"Sandbox"|"Production"|"Oda")
log "✅ Environment '$ENVIRONMENT' is valid"
;;
*)
error "❌ Invalid environment '$ENVIRONMENT'. Use: Development, Sandbox, Production, or Oda"
;;
esac
# Helper function to start PostgreSQL for Development (if still using Docker Compose)
start_postgres_if_needed() {
if [ "$ENVIRONMENT" = "Development" ] || [ "$ENVIRONMENT" = "Oda" ]; then # Assuming Oda also uses local Docker
log "🔍 Checking if PostgreSQL is running for $ENVIRONMENT..."
if ! docker ps --filter "name=postgres" --format "{{.Names}}" | grep -q "postgres"; then
log "🐳 Starting PostgreSQL container for $ENVIRONMENT from $DOCKER_DIR..."
# Execute docker-compose from the DOCKER_DIR
(cd "$DOCKER_DIR" && docker-compose -f docker-compose.yml -f docker-compose.local.yml up -d postgres) || error "Failed to start PostgreSQL container."
log "⏳ Waiting for PostgreSQL to be ready (15 seconds)..."
sleep 15
else
log "✅ PostgreSQL container is already running."
fi
fi
}
# Helper function to extract connection details from appsettings
extract_connection_details() {
local appsettings_file="$API_PROJECT_PATH/appsettings.$ENVIRONMENT.json"
local default_appsettings="$API_PROJECT_PATH/appsettings.json"
# Try environment-specific file first, then default
if [ -f "$appsettings_file" ]; then
log "📋 Reading connection string from: appsettings.$ENVIRONMENT.json"
# Look for PostgreSql.ConnectionString first, then fallback to ConnectionString
CONNECTION_STRING=$(grep -A 3 '"PostgreSql"' "$appsettings_file" | grep -o '"ConnectionString": *"[^"]*"' | cut -d'"' -f4)
if [ -z "$CONNECTION_STRING" ]; then
CONNECTION_STRING=$(grep -o '"ConnectionString": *"[^"]*"' "$appsettings_file" | cut -d'"' -f4)
fi
elif [ -f "$default_appsettings" ]; then
log "📋 Reading connection string from: appsettings.json (default)"
# Look for PostgreSql.ConnectionString first, then fallback to ConnectionString
CONNECTION_STRING=$(grep -A 3 '"PostgreSql"' "$default_appsettings" | grep -o '"ConnectionString": *"[^"]*"' | cut -d'"' -f4)
if [ -z "$CONNECTION_STRING" ]; then
CONNECTION_STRING=$(grep -o '"ConnectionString": *"[^"]*"' "$default_appsettings" | cut -d'"' -f4)
fi
else
warn "⚠️ Could not find appsettings file for environment $ENVIRONMENT"
return 1
fi
if [ -z "$CONNECTION_STRING" ]; then
error "❌ Could not extract connection string from appsettings file"
return 1
fi
log "📋 Found connection string: $CONNECTION_STRING"
# Parse connection string
DB_HOST=$(echo "$CONNECTION_STRING" | grep -o 'Host=[^;]*' | cut -d'=' -f2)
DB_PORT=$(echo "$CONNECTION_STRING" | grep -o 'Port=[^;]*' | cut -d'=' -f2)
DB_NAME=$(echo "$CONNECTION_STRING" | grep -o 'Database=[^;]*' | cut -d'=' -f2)
DB_USER=$(echo "$CONNECTION_STRING" | grep -o 'Username=[^;]*' | cut -d'=' -f2)
DB_PASSWORD=$(echo "$CONNECTION_STRING" | grep -o 'Password=[^;]*' | cut -d'=' -f2)
# Set defaults if not found
DB_HOST=${DB_HOST:-"localhost"}
DB_PORT=${DB_PORT:-"5432"}
DB_NAME=${DB_NAME:-"postgres"}
DB_USER=${DB_USER:-"postgres"}
DB_PASSWORD=${DB_PASSWORD:-"postgres"}
log "📋 Extracted connection details: $DB_HOST:$DB_PORT/$DB_NAME (user: $DB_USER, password: $DB_PASSWORD)"
}
# Helper function to test PostgreSQL connectivity
test_postgres_connectivity() {
if ! command -v psql >/dev/null 2>&1; then
warn "⚠️ psql not available, skipping PostgreSQL connectivity test"
return 0
fi
log "🔍 Testing PostgreSQL connectivity with psql..."
# For remote servers or when target database might not exist, test with postgres database first
local test_database="$DB_NAME"
if [ "$TARGET_DB_EXISTS" = "false" ]; then
test_database="postgres"
log "🔍 Target database doesn't exist, testing connectivity with 'postgres' database..."
fi
# Test basic connectivity
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$test_database" -c "SELECT version();" >/dev/null 2>&1; then
log "✅ PostgreSQL connectivity test passed"
# Get database info
log "📊 Database Information:"
DB_INFO=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$test_database" -t -c "
SELECT
'Database: ' || current_database() || ' (Size: ' || pg_size_pretty(pg_database_size(current_database())) || ')',
'PostgreSQL Version: ' || version(),
'Connection: ' || inet_server_addr() || ':' || inet_server_port()
" 2>/dev/null | tr '\n' ' ')
log " $DB_INFO"
# Only check migrations if we're testing the actual target database
if [ "$test_database" = "$DB_NAME" ]; then
# Check if __EFMigrationsHistory table exists
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\dt __EFMigrationsHistory" >/dev/null 2>&1; then
log "✅ EF Core migrations history table exists"
# Count applied migrations
MIGRATION_COUNT=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM \"__EFMigrationsHistory\";" 2>/dev/null | tr -d ' ')
log "📋 Applied migrations count: $MIGRATION_COUNT"
# Show recent migrations
if [ "$MIGRATION_COUNT" -gt 0 ]; then
log "📋 Recent migrations:"
RECENT_MIGRATIONS=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
SELECT \"MigrationId\" FROM \"__EFMigrationsHistory\"
ORDER BY \"MigrationId\" DESC
LIMIT 5;
" 2>/dev/null | sed 's/^/ /')
echo "$RECENT_MIGRATIONS"
fi
else
warn "⚠️ EF Core migrations history table not found - database may be empty"
fi
else
log "📋 Connectivity test completed using 'postgres' database (target database will be created)"
fi
return 0
else
error "❌ PostgreSQL connectivity test failed"
error " Host: $DB_HOST, Port: $DB_PORT, Database: $test_database, User: $DB_USER"
return 1
fi
}
# --- Core Logic ---
# No global 'cd' needed here. All paths are now absolute.
# This makes the script much more robust to where it's executed from.
# Set ASPNETCORE_ENVIRONMENT to load the correct appsettings
export ASPNETCORE_ENVIRONMENT="$ENVIRONMENT"
log "ASPNETCORE_ENVIRONMENT set to: $ASPNETCORE_ENVIRONMENT"
# If Development or Oda, start local PostgreSQL
start_postgres_if_needed
# Extract connection details from appsettings
extract_connection_details
# Step 0: Build the .NET projects to ensure they are up to date
log "🔨 Step 0: Building .NET projects..."
log "🔧 Building Managing.Infrastructure.Database project..."
if (cd "$DB_PROJECT_PATH" && dotnet build); then
log "✅ Managing.Infrastructure.Database project built successfully"
else
error "❌ Failed to build Managing.Infrastructure.Database project"
fi
log "🔧 Building Managing.Api project..."
if (cd "$API_PROJECT_PATH" && dotnet build); then
log "✅ Managing.Api project built successfully"
else
error "❌ Failed to build Managing.Api project"
fi
# Step 1: Check Database Connection and Create if Needed
log "🔧 Step 1: Checking database connection and creating database if needed..."
# Log the environment and expected connection details (for user info, still relies on appsettings)
log "🔧 Using environment: $ENVIRONMENT"
log "📋 Connection details: $DB_HOST:$DB_PORT/$DB_NAME (user: $DB_USER)"
# Initial connectivity check - test if we can reach the database server
log "🔍 Step 1a: Testing basic database server connectivity..."
if command -v psql >/dev/null 2>&1; then
# Test if we can connect to the postgres database (which should always exist)
log "🔍 Connecting to default 'postgres' database to verify server connectivity..."
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "SELECT 1;" >/dev/null 2>&1; then
log "✅ Database server connectivity test passed"
# Check if our target database exists
log "🔍 Checking if target database '$DB_NAME' exists..."
DB_EXISTS=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -t -c "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME';" 2>/dev/null | tr -d ' ')
if [ "$DB_EXISTS" = "1" ]; then
log "✅ Target database '$DB_NAME' exists"
TARGET_DB_EXISTS=true
else
log "⚠️ Target database '$DB_NAME' does not exist - will be created"
TARGET_DB_EXISTS=false
fi
else
error "❌ Database server connectivity test failed"
error " Cannot reach PostgreSQL server at $DB_HOST:$DB_PORT with database 'postgres'"
error " Please verify:"
error " - Database server is running"
error " - Network connectivity to $DB_HOST:$DB_PORT"
error " - Credentials are correct (user: $DB_USER)"
error " - Firewall settings allow connections"
error " - The 'postgres' database exists (default PostgreSQL database)"
fi
else
# Fallback: try to connect using EF Core to test basic connectivity
log "🔄 psql not available, testing connectivity via EF Core..."
if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then
log "✅ Database server connectivity test passed (via EF Core)"
TARGET_DB_EXISTS=true # Assume it exists if EF Core can connect
else
warn "⚠️ Could not verify database server connectivity (psql not available)"
warn " Proceeding with caution - connectivity will be tested during migration"
TARGET_DB_EXISTS=false # Assume it doesn't exist if EF Core can't connect
fi
fi
log "🔍 Step 1b: Testing database connection and checking if database exists via EF CLI..."
# Test connection by listing migrations. If it fails, the database likely doesn't exist or is inaccessible.
# Execute dotnet ef from DB_PROJECT_PATH for correct context, but pass API_PROJECT_PATH as startup.
# Since we just built the projects, we can safely use --no-build flag for faster execution
if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then
log "✅ EF Core database connection successful and database appears to exist."
# Now test with psql for additional verification (this will use postgres db if target doesn't exist)
test_postgres_connectivity
# If psql connectivity test fails, stop the migration
if [ $? -ne 0 ]; then
error "❌ PostgreSQL connectivity test failed. Migration aborted for safety."
error " Please verify your database connection and try again."
fi
else
# Database doesn't exist or connection failed
if [ "$TARGET_DB_EXISTS" = "false" ]; then
log "📝 Database '$DB_NAME' does not exist. Creating database and applying migrations..."
# Test connectivity with postgres database first (since target doesn't exist)
test_postgres_connectivity
# If connectivity test fails, stop the migration
if [ $? -ne 0 ]; then
error "❌ PostgreSQL connectivity test failed. Cannot proceed with database creation."
error " Please verify your connection settings and try again."
fi
# Step 1: Create the database first
log "🔧 Step 1: Creating database '$DB_NAME'..."
if command -v psql >/dev/null 2>&1; then
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE \"$DB_NAME\";" >/dev/null 2>&1; then
log "✅ Database '$DB_NAME' created successfully"
else
error "❌ Failed to create database '$DB_NAME'"
error " Please verify you have sufficient privileges to create databases."
fi
else
warn "⚠️ psql not available, attempting to create database via EF Core..."
# EF Core will attempt to create the database during update
fi
# Step 2: Generate migration script for the new database
log "📝 Step 2: Generating migration script for new database..."
TEMP_MIGRATION_SCRIPT="$BACKUP_DIR/temp_migration_${ENVIRONMENT}_${TIMESTAMP}.sql"
if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$TEMP_MIGRATION_SCRIPT"); then
log "✅ Migration script generated successfully: $(basename "$TEMP_MIGRATION_SCRIPT")"
# Step 3: Apply the migration script to the new database
log "🔧 Step 3: Applying migration script to new database..."
if command -v psql >/dev/null 2>&1; then
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$TEMP_MIGRATION_SCRIPT" >/dev/null 2>&1; then
log "✅ Migration script applied successfully to new database"
else
error "❌ Failed to apply migration script to newly created database"
fi
else
# Fallback to EF Core database update
log "🔄 psql not available, using EF Core to apply migrations..."
if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then
log "✅ Database created and initialized successfully using EF Core"
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true )
error "❌ Failed to create and initialize database using EF Core."
error " EF CLI Output: $ERROR_OUTPUT"
fi
fi
# Clean up temporary migration script
rm -f "$TEMP_MIGRATION_SCRIPT"
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$TEMP_MIGRATION_SCRIPT") 2>&1 || true )
error "❌ Failed to generate migration script."
error " EF CLI Output: $ERROR_OUTPUT"
fi
else
warn "⚠️ Database connection failed but database may exist. Attempting to update existing database..."
# Try to update the existing database
if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then
log "✅ Database updated successfully"
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true )
error "❌ Failed to update database."
error " EF CLI Output: $ERROR_OUTPUT"
error " This usually means the connection string in your .NET project's appsettings.$ENVIRONMENT.json is incorrect,"
error " or the database server is not running/accessible for environment '$ENVIRONMENT'."
fi
fi
# Test connectivity after creation/update
test_postgres_connectivity
# If connectivity test fails after creation, stop the migration
if [ $? -ne 0 ]; then
error "❌ PostgreSQL connectivity test failed after database creation. Migration aborted for safety."
error " Database may have been created but is not accessible. Please verify your connection settings."
fi
fi
# Final verification of connection
log "🔍 Verifying database connection post-creation/update..."
if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then
log "✅ Database connectivity verification passed."
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true )
error "❌ Final database connectivity verification failed."
error " EF CLI Output: $ERROR_OUTPUT"
error " This is critical. Please review the previous error messages and your connection string for '$ENVIRONMENT'."
fi
# Step 2: Create Backup
log "📦 Step 2: Creating database backup using pg_dump..."
# Define the actual backup file path (absolute)
BACKUP_FILE="$BACKUP_DIR/managing_${ENVIRONMENT}_backup_${TIMESTAMP}.sql"
# Backup file display path (relative to script execution)
BACKUP_FILE_DISPLAY="$BACKUP_DIR_NAME/$ENVIRONMENT/managing_${ENVIRONMENT}_backup_${TIMESTAMP}.sql"
# Create backup with retry logic
BACKUP_SUCCESS=false
for attempt in 1 2 3; do
log "Backup attempt $attempt/3..."
# Create real database backup using pg_dump
if command -v pg_dump >/dev/null 2>&1; then
if PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" --no-password --verbose --clean --if-exists --create --format=plain > "$BACKUP_FILE" 2>/dev/null; then
log "✅ Database backup created using pg_dump: $BACKUP_FILE_DISPLAY"
BACKUP_SUCCESS=true
break
else
# If pg_dump fails, fall back to EF Core migration script
warn "⚠️ pg_dump failed, falling back to EF Core migration script..."
if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE"); then
log "✅ EF Core Migration SQL Script generated: $BACKUP_FILE_DISPLAY"
BACKUP_SUCCESS=true
break
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE") 2>&1 || true)
if [ $attempt -lt 3 ]; then
warn "⚠️ Backup attempt $attempt failed. Retrying in 5 seconds..."
warn " EF CLI Output: $ERROR_OUTPUT"
sleep 5
else
error "❌ Database backup failed after 3 attempts."
error " EF CLI Output: $ERROR_OUTPUT"
error " Migration aborted for safety reasons."
fi
fi
fi
else
# If pg_dump is not available, use EF Core migration script
warn "⚠️ pg_dump not available, using EF Core migration script for backup..."
if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE"); then
log "✅ EF Core Migration SQL Script generated: $BACKUP_FILE_DISPLAY"
BACKUP_SUCCESS=true
break
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE") 2>&1 || true)
if [ $attempt -lt 3 ]; then
warn "⚠️ Backup attempt $attempt failed. Retrying in 5 seconds..."
warn " EF CLI Output: $ERROR_OUTPUT"
sleep 5
else
error "❌ Database backup failed after 3 attempts."
error " EF CLI Output: $ERROR_OUTPUT"
error " Migration aborted for safety reasons."
fi
fi
fi
done
# Check if backup was successful before proceeding
if [ "$BACKUP_SUCCESS" != "true" ]; then
error "❌ Database backup failed. Migration aborted for safety."
error " Cannot proceed with migration without a valid backup."
error " Please resolve backup issues and try again."
fi
# Step 3: Run Migration (This effectively is a retry if previous "update" failed, or a final apply)
log "🔄 Step 3: Running database migration (final application of pending migrations)..."
# Check if database exists and create it if needed before applying migrations
log "🔍 Step 3a: Ensuring target database exists..."
if [ "$TARGET_DB_EXISTS" = "false" ]; then
log "🔧 Creating database '$DB_NAME'..."
if command -v psql >/dev/null 2>&1; then
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE \"$DB_NAME\";" >/dev/null 2>&1; then
log "✅ Database '$DB_NAME' created successfully"
else
error "❌ Failed to create database '$DB_NAME'"
error " Please verify you have sufficient privileges to create databases."
fi
else
error "❌ psql not available, cannot create database. Please create database '$DB_NAME' manually."
fi
fi
# Generate migration script first (Microsoft recommended approach)
MIGRATION_SCRIPT="$BACKUP_DIR/migration_${ENVIRONMENT}_${TIMESTAMP}.sql"
log "📝 Step 3b: Generating migration script for pending migrations..."
if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$MIGRATION_SCRIPT"); then
log "✅ Migration script generated: $(basename "$MIGRATION_SCRIPT")"
# Show the migration script path to the user for review
echo ""
echo "=========================================="
echo "📋 MIGRATION SCRIPT READY FOR REVIEW"
echo "=========================================="
echo "Generated script: $MIGRATION_SCRIPT"
echo "Environment: $ENVIRONMENT"
echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
echo ""
echo "⚠️ IMPORTANT: Please review the migration script before proceeding!"
echo " You can examine the script with: cat $MIGRATION_SCRIPT"
echo " Or open it in your editor to review the changes."
echo ""
echo "=========================================="
echo ""
# Ask for user confirmation
read -p "🔍 Have you reviewed the migration script and want to proceed? Type 'yes' to continue: " user_confirmation
if [ "$user_confirmation" != "yes" ]; then
log "❌ Migration cancelled by user."
log " Migration script is available at: $(basename "$MIGRATION_SCRIPT")"
log " You can apply it manually later with:"
log " PGPASSWORD=\"$DB_PASSWORD\" psql -h \"$DB_HOST\" -p \"$DB_PORT\" -U \"$DB_USER\" -d \"$DB_NAME\" -f \"$MIGRATION_SCRIPT\""
exit 0
fi
log "✅ User confirmed migration. Proceeding with database update..."
# Apply the migration script using psql (recommended approach)
log "🔧 Step 3c: Applying migration script to database..."
if command -v psql >/dev/null 2>&1; then
if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_SCRIPT" >/dev/null 2>&1; then
log "✅ Migration script applied successfully to database"
else
ERROR_OUTPUT=$( (PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_SCRIPT") 2>&1 || true )
error "❌ Failed to apply migration script to database"
error " PSQL Output: $ERROR_OUTPUT"
error " Migration script available at: $(basename "$MIGRATION_SCRIPT")"
fi
else
# Fallback to EF Core database update if psql is not available
log "🔄 psql not available, falling back to EF Core database update..."
if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then
log "✅ Database migration completed successfully using EF Core."
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true )
error "❌ Database migration failed during final update."
error " EF CLI Output: $ERROR_OUTPUT"
error " Check the .NET project logs for detailed errors."
error " Backup script available at: $BACKUP_FILE_DISPLAY"
fi
fi
# Clean up migration script after successful application
rm -f "$MIGRATION_SCRIPT"
else
ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$MIGRATION_SCRIPT") 2>&1 || true )
error "❌ Failed to generate migration script."
error " EF CLI Output: $ERROR_OUTPUT"
error " Check the .NET project logs for detailed errors."
error " Backup script available at: $BACKUP_FILE_DISPLAY"
fi
# Step 4: Verify Migration
log "🔍 Step 4: Verifying migration status..."
# List migrations to check applied status
MIGRATION_LIST_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 )
log "📋 Current migration status:\n$MIGRATION_LIST_OUTPUT"
# Check if there are any pending migrations after update
PENDING_MIGRATIONS=$(echo "$MIGRATION_LIST_OUTPUT" | grep -c "\[ \]" || echo "0")
PENDING_MIGRATIONS=$(echo "$PENDING_MIGRATIONS" | tr -d '\n') # Remove any newlines
if [ "$PENDING_MIGRATIONS" -gt 0 ]; then
warn "⚠️ WARNING: $PENDING_MIGRATIONS pending migration(s) found after update."
warn " This indicates the 'dotnet ef database update' command may not have fully completed."
else
log "✅ All migrations appear to be applied successfully."
fi
# --- Step 5: Cleanup Backups (keep only 5 dumps max) ---
log "🧹 Step 5: Cleaning up old backups..."
# Keep only the last 5 backups for this environment (in the environment-specific subfolder)
ls -t "$BACKUP_DIR"/managing_${ENVIRONMENT}_backup_*.sql 2>/dev/null | tail -n +6 | xargs -r rm -f || true # Added -f for force removal
log "✅ Kept last 5 backups for $ENVIRONMENT environment in $BACKUP_DIR_NAME/$ENVIRONMENT/"
# Success Summary
log "🎉 Migration completed successfully for environment: $ENVIRONMENT!"
log "📁 EF Core Migration SQL Script: $BACKUP_FILE_DISPLAY"
log "📝 Full Log file: $LOG_FILE"
echo ""
echo "=========================================="
echo "📋 MIGRATION SUMMARY"
echo "=========================================="
echo "Environment: $ENVIRONMENT"
echo "Timestamp: $TIMESTAMP"
echo "Status: ✅ SUCCESS"
echo "EF Core SQL Backup: $BACKUP_FILE_DISPLAY"
echo "Log: $LOG_FILE"
echo "=========================================="

View File

@@ -18,9 +18,10 @@ public class WorkerController : ControllerBase
} }
[HttpGet] [HttpGet]
public ActionResult<List<Worker>> GetWorkers() public async Task<ActionResult<List<Worker>>> GetWorkers()
{ {
return Ok(_workerService.GetWorkers()); var workers = await _workerService.GetWorkers();
return Ok(workers.ToList());
} }
[HttpPatch] [HttpPatch]

View File

@@ -6,10 +6,10 @@ using Managing.Bootstrap;
using Managing.Common; using Managing.Common;
using Managing.Core.Middleawares; using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models; using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb; using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm.Models.Privy; using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using NSwag; using NSwag;
@@ -27,9 +27,9 @@ builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json"); .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"];
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
var postgreSqlConnectionString = builder.Configuration.GetSection("PostgreSql")["ConnectionString"];
// Initialize Sentry // Initialize Sentry
SentrySdk.Init(options => SentrySdk.Init(options =>
@@ -61,7 +61,6 @@ builder.Services.AddServiceDiscovery();
// Configure health checks // Configure health checks
builder.Services.AddHealthChecks() builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]) .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]); .AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
@@ -91,7 +90,6 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
.WriteTo.Elasticsearch(es); .WriteTo.Elasticsearch(es);
}); });
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy)); builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options => builder.Services.AddControllers().AddJsonOptions(options =>
@@ -108,6 +106,27 @@ builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
})); }));
builder.Services.AddSignalR().AddJsonProtocol(); builder.Services.AddSignalR().AddJsonProtocol();
// Add PostgreSQL DbContext for worker services
builder.Services.AddDbContext<ManagingDbContext>(options =>
{
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
{
npgsqlOptions.CommandTimeout(60);
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
errorCodesToAdd: null);
});
if (builder.Environment.IsDevelopment())
{
options.EnableDetailedErrors();
options.EnableSensitiveDataLogging();
options.EnableThreadSafetyChecks();
}
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
options.EnableServiceProviderCaching();
options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning);
}, ServiceLifetime.Scoped);
builder.Services.RegisterWorkersDependencies(builder.Configuration); builder.Services.RegisterWorkersDependencies(builder.Configuration);
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document => builder.Services.AddOpenApiDocument(document =>
@@ -157,24 +176,6 @@ builder.WebHost.SetupDiscordBot();
var app = builder.Build(); var app = builder.Build();
app.UseSerilogRequestLogging(); app.UseSerilogRequestLogging();
// Create MongoDB indexes on startup
try
{
var indexService = app.Services.GetRequiredService<IndexService>();
await indexService.CreateIndexesAsync();
}
catch (Exception ex)
{
// Log the error but don't fail the application startup
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes.");
}
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOpenApi(); app.UseOpenApi();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://localhost:8086/", "Url": "http://localhost:8086/",
"Token": "" "Token": ""

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "https://influx-db.apps.managing.live", "Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://influxdb:8086/", "Url": "http://influxdb:8086/",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://localhost:8086/", "Url": "http://localhost:8086/",
"Organization": "managing-org", "Organization": "managing-org",
@@ -37,5 +33,6 @@
"WorkerTraderWatcher": false, "WorkerTraderWatcher": false,
"WorkerLeaderboard": false, "WorkerLeaderboard": false,
"WorkerFundingRatesWatcher": false, "WorkerFundingRatesWatcher": false,
"WorkerGeneticAlgorithm": true "WorkerGeneticAlgorithm": false,
"WorkerBundleBacktest": true
} }

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://root:gRerdhtg546FgrW@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "https://influx-db.apps.managing.live", "Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://srv-captain--influx-db:8086/", "Url": "http://srv-captain--influx-db:8086/",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "https://influx-db.apps.managing.live", "Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,13 +1,12 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://influxdb:8086/", "Url": "http://influxdb:8086/",
"Organization": "", "Organization": "",
"Token": "" "Token": ""
}, },
"PostgreSql": {
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
},
"Serilog": { "Serilog": {
"MinimumLevel": { "MinimumLevel": {
"Default": "Information", "Default": "Information",
@@ -17,6 +16,9 @@
} }
} }
}, },
"N8n": {
"WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
},
"ElasticConfiguration": { "ElasticConfiguration": {
"Uri": "http://elasticsearch:9200" "Uri": "http://elasticsearch:9200"
}, },

View File

@@ -102,13 +102,13 @@ namespace Managing.Api.Controllers
{ {
var user = await GetUser(); var user = await GetUser();
var result = await _AccountService.SwapGmxTokensAsync( var result = await _AccountService.SwapGmxTokensAsync(
user, user,
name, name,
request.FromTicker, request.FromTicker,
request.ToTicker, request.ToTicker,
request.Amount, request.Amount,
request.OrderType, request.OrderType,
request.TriggerRatio, request.TriggerRatio,
request.AllowedSlippage request.AllowedSlippage
); );
return Ok(result); return Ok(result);
@@ -126,11 +126,11 @@ namespace Managing.Api.Controllers
{ {
var user = await GetUser(); var user = await GetUser();
var result = await _AccountService.SendTokenAsync( var result = await _AccountService.SendTokenAsync(
user, user,
name, name,
request.RecipientAddress, request.RecipientAddress,
request.Ticker, request.Ticker,
request.Amount, request.Amount,
request.ChainId request.ChainId
); );
return Ok(result); return Ok(result);
@@ -142,9 +142,9 @@ namespace Managing.Api.Controllers
/// <param name="name">The name of the account to delete.</param> /// <param name="name">The name of the account to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns> /// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete] [HttpDelete]
public ActionResult DeleteAccount(string name) public async Task<ActionResult> DeleteAccount(string name)
{ {
var user = GetUser().Result; var user = await GetUser();
return Ok(_AccountService.DeleteAccount(user, name)); return Ok(_AccountService.DeleteAccount(user, name));
} }
} }

View File

@@ -70,7 +70,8 @@ public class BacktestController : BaseController
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests() public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_backtester.GetBacktestsByUser(user)); var backtests = await _backtester.GetBacktestsByUserAsync(user);
return Ok(backtests);
} }
/// <summary> /// <summary>
@@ -84,7 +85,7 @@ public class BacktestController : BaseController
public async Task<ActionResult<Backtest>> Backtest(string id) public async Task<ActionResult<Backtest>> Backtest(string id)
{ {
var user = await GetUser(); var user = await GetUser();
var backtest = _backtester.GetBacktestByIdForUser(user, id); var backtest = await _backtester.GetBacktestByIdForUserAsync(user, id);
if (backtest == null) if (backtest == null)
{ {
@@ -103,7 +104,8 @@ public class BacktestController : BaseController
public async Task<ActionResult> DeleteBacktest(string id) public async Task<ActionResult> DeleteBacktest(string id)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_backtester.DeleteBacktestByUser(user, id)); var result = await _backtester.DeleteBacktestByUserAsync(user, id);
return Ok(result);
} }
/// <summary> /// <summary>
@@ -115,7 +117,7 @@ public class BacktestController : BaseController
public async Task<ActionResult> DeleteBacktests([FromBody] DeleteBacktestsRequest request) public async Task<ActionResult> DeleteBacktests([FromBody] DeleteBacktestsRequest request)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_backtester.DeleteBacktestsByIdsForUser(user, request.BacktestIds)); return Ok(await _backtester.DeleteBacktestsByIdsForUserAsync(user, request.BacktestIds));
} }
/// <summary> /// <summary>
@@ -133,7 +135,7 @@ public class BacktestController : BaseController
return BadRequest("Request ID is required"); return BadRequest("Request ID is required");
} }
var backtests = _backtester.GetBacktestsByRequestId(requestId); var backtests = await _backtester.GetBacktestsByRequestIdAsync(requestId);
return Ok(backtests); return Ok(backtests);
} }
@@ -177,7 +179,7 @@ public class BacktestController : BaseController
} }
var (backtests, totalCount) = var (backtests, totalCount) =
_backtester.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder); await _backtester.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy, sortOrder);
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
@@ -243,7 +245,7 @@ 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) = _backtester.GetBacktestsByUserPaginated(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
@@ -528,7 +530,7 @@ public class BacktestController : BaseController
_backtester.DeleteBundleBacktestRequestByIdForUser(user, id); _backtester.DeleteBundleBacktestRequestByIdForUser(user, id);
// Then, delete all related backtests // Then, delete all related backtests
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id); var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
return Ok(new return Ok(new
{ {
@@ -693,7 +695,7 @@ public class BacktestController : BaseController
_geneticService.DeleteGeneticRequestByIdForUser(user, id); _geneticService.DeleteGeneticRequestByIdForUser(user, id);
// Then, delete all related backtests // Then, delete all related backtests
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id); var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
return Ok(new return Ok(new
{ {

View File

@@ -22,17 +22,16 @@ public abstract class BaseController : ControllerBase
var identity = HttpContext?.User.Identity as ClaimsIdentity; var identity = HttpContext?.User.Identity as ClaimsIdentity;
if (identity != null) if (identity != null)
{ {
var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value; var address = identity.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
var user = await _userService.GetUserByAddressAsync(address); if (address != null)
{
if (user != null) var user = await _userService.GetUserByAddressAsync(address);
return user; return user;
}
throw new Exception("User not found for this token"); throw new Exception("User not found for this token");
} }
throw new Exception("Not identity assigned to this token"); throw new Exception("Not identity assigned to this token");
} }
} }

View File

@@ -128,8 +128,14 @@ public class BotController : BaseController
var user = await GetUser(); var user = await GetUser();
if (string.IsNullOrEmpty(user.AgentName))
{
return BadRequest(
"Agent name is required to start a bot. Please configure your agent name in the user profile.");
}
// Get money management - either by name lookup or use provided object // Get money management - either by name lookup or use provided object
MoneyManagement moneyManagement; LightMoneyManagement moneyManagement;
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName)) if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
{ {
moneyManagement = moneyManagement =
@@ -144,12 +150,6 @@ public class BotController : BaseController
moneyManagement = Map(request.Config.MoneyManagement); moneyManagement = Map(request.Config.MoneyManagement);
// Format percentage values if using custom money management // Format percentage values if using custom money management
moneyManagement?.FormatPercentage(); moneyManagement?.FormatPercentage();
// Ensure user is set for custom money management
if (moneyManagement != null)
{
moneyManagement.User = user;
}
} }
// Validate initialTradingBalance // Validate initialTradingBalance
@@ -425,7 +425,7 @@ public class BotController : BaseController
new StopBotCommand(bot.Identifier)); new StopBotCommand(bot.Identifier));
// Get the saved bot backup // Get the saved bot backup
var backup = _botService.GetBotBackup(bot.Identifier); var backup = await _botService.GetBotBackup(bot.Identifier);
if (backup != null) if (backup != null)
{ {
_botService.StartBotFromBackup(backup); _botService.StartBotFromBackup(backup);
@@ -564,7 +564,8 @@ public class BotController : BaseController
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error opening position manually"); _logger.LogError(ex, "Error opening position manually");
return StatusCode(500, $"Error opening position: {ex.Message}"); return StatusCode(500,
$"Error opening position: {ex.Message}, {ex.InnerException?.Message} or {ex.StackTrace}");
} }
} }
@@ -699,20 +700,31 @@ public class BotController : BaseController
} }
// Validate and get the money management // Validate and get the money management
MoneyManagement moneyManagement = null; LightMoneyManagement moneyManagement = null;
if (!string.IsNullOrEmpty(request.MoneyManagementName)) if (!string.IsNullOrEmpty(request.MoneyManagementName))
{ {
// Load money management by name // Load money management by name
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName); var fullMoneyManagement =
if (moneyManagement == null) await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (fullMoneyManagement == null)
{ {
return BadRequest($"Money management '{request.MoneyManagementName}' not found"); return BadRequest($"Money management '{request.MoneyManagementName}' not found");
} }
if (moneyManagement.User?.Name != user.Name) if (fullMoneyManagement.User?.Name != user.Name)
{ {
return Forbid("You don't have permission to use this money management"); return Forbid("You don't have permission to use this money management");
} }
// Convert to LightMoneyManagement
moneyManagement = new LightMoneyManagement
{
Name = fullMoneyManagement.Name,
Timeframe = fullMoneyManagement.Timeframe,
StopLoss = fullMoneyManagement.StopLoss,
TakeProfit = fullMoneyManagement.TakeProfit,
Leverage = fullMoneyManagement.Leverage
};
} }
else if (request.MoneyManagement != null) else if (request.MoneyManagement != null)
{ {
@@ -720,9 +732,6 @@ public class BotController : BaseController
moneyManagement = request.MoneyManagement; moneyManagement = request.MoneyManagement;
// Format percentage values if using custom money management // Format percentage values if using custom money management
moneyManagement.FormatPercentage(); moneyManagement.FormatPercentage();
// Ensure user is set for custom money management
moneyManagement.User = user;
} }
else else
{ {

View File

@@ -206,14 +206,14 @@ public class DataController : ControllerBase
/// <returns>A <see cref="SpotlightOverview"/> object containing spotlight data.</returns> /// <returns>A <see cref="SpotlightOverview"/> object containing spotlight data.</returns>
[Authorize] [Authorize]
[HttpGet("Spotlight")] [HttpGet("Spotlight")]
public ActionResult<SpotlightOverview> GetSpotlight() public async Task<ActionResult<SpotlightOverview>> GetSpotlight()
{ {
var overview = _cacheService.GetOrSave(nameof(SpotlightOverview), var cacheKey = $"Spotlight_{DateTime.Now.AddDays(-2).ToString("yyyy-MM-dd")}";
() => { return _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); }, TimeSpan.FromMinutes(2)); var overview = _cacheService.GetValue<SpotlightOverview>(cacheKey);
if (overview == null)
if (overview?.Spotlights.Count < overview?.ScenarioCount || overview == null)
{ {
overview = _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); overview = await _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2));
_cacheService.SaveValue(cacheKey, overview, TimeSpan.FromMinutes(2));
} }
return Ok(overview); return Ok(overview);
@@ -256,7 +256,7 @@ public class DataController : ControllerBase
{ {
// Map ScenarioRequest to domain Scenario object // Map ScenarioRequest to domain Scenario object
var domainScenario = MapScenarioRequestToScenario(request.Scenario); var domainScenario = MapScenarioRequestToScenario(request.Scenario);
indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles); indicatorsValues = _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
} }
return Ok(new CandlesWithIndicatorsResponse return Ok(new CandlesWithIndicatorsResponse

View File

@@ -40,8 +40,16 @@ public class MoneyManagementController : BaseController
[HttpPost] [HttpPost]
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement) public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
{ {
var user = await GetUser(); try
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement)); {
var user = await GetUser();
var result = await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement);
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, $"Error creating/updating money management: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -52,8 +60,16 @@ public class MoneyManagementController : BaseController
[Route("moneymanagements")] [Route("moneymanagements")]
public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements() public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements()
{ {
var user = await GetUser(); try
return Ok(_moneyManagementService.GetMoneyMangements(user)); {
var user = await GetUser();
var moneyManagements = await _moneyManagementService.GetMoneyMangements(user);
return Ok(moneyManagements);
}
catch (Exception ex)
{
return StatusCode(500, $"Error retrieving money managements: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -64,8 +80,22 @@ public class MoneyManagementController : BaseController
[HttpGet] [HttpGet]
public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name) public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name)
{ {
var user = await GetUser(); try
return Ok(await _moneyManagementService.GetMoneyMangement(user, name)); {
var user = await GetUser();
var result = await _moneyManagementService.GetMoneyMangement(user, name);
if (result == null)
{
return NotFound($"Money management strategy '{name}' not found");
}
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, $"Error retrieving money management: {ex.Message}");
}
} }
/// <summary> /// <summary>
@@ -76,7 +106,21 @@ public class MoneyManagementController : BaseController
[HttpDelete] [HttpDelete]
public async Task<ActionResult> DeleteMoneyManagement(string name) public async Task<ActionResult> DeleteMoneyManagement(string name)
{ {
var user = await GetUser(); try
return Ok(_moneyManagementService.DeleteMoneyManagement(user, name)); {
var user = await GetUser();
var result = await _moneyManagementService.DeleteMoneyManagement(user, name);
if (!result)
{
return NotFound($"Money management strategy '{name}' not found or could not be deleted");
}
return Ok(new { success = true, message = $"Money management strategy '{name}' deleted successfully" });
}
catch (Exception ex)
{
return StatusCode(500, $"Error deleting money management: {ex.Message}");
}
} }
} }

View File

@@ -43,7 +43,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult<IEnumerable<ScenarioViewModel>>> GetScenarios() public async Task<ActionResult<IEnumerable<ScenarioViewModel>>> GetScenarios()
{ {
var user = await GetUser(); var user = await GetUser();
var scenarios = _scenarioService.GetScenariosByUser(user); var scenarios = await _scenarioService.GetScenariosByUserAsync(user);
var scenarioViewModels = scenarios.Select(MapToScenarioViewModel); var scenarioViewModels = scenarios.Select(MapToScenarioViewModel);
return Ok(scenarioViewModels); return Ok(scenarioViewModels);
} }
@@ -59,7 +59,7 @@ public class ScenarioController : BaseController
int? loopbackPeriod = null) int? loopbackPeriod = null)
{ {
var user = await GetUser(); var user = await GetUser();
var scenario = _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod); var scenario = await _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod);
var scenarioViewModel = MapToScenarioViewModel(scenario); var scenarioViewModel = MapToScenarioViewModel(scenario);
return Ok(scenarioViewModel); return Ok(scenarioViewModel);
} }
@@ -73,7 +73,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> DeleteScenario(string name) public async Task<ActionResult> DeleteScenario(string name)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_scenarioService.DeleteScenarioByUser(user, name)); return Ok(await _scenarioService.DeleteScenarioByUser(user, name));
} }
// Update scenario // Update scenario
@@ -81,7 +81,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null) public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod)); return Ok(await _scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
} }
/// <summary> /// <summary>
@@ -93,7 +93,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators() public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators()
{ {
var user = await GetUser(); var user = await GetUser();
var indicators = _scenarioService.GetIndicatorsByUser(user); var indicators = await _scenarioService.GetIndicatorsAsync();
var indicatorViewModels = indicators.Select(MapToIndicatorViewModel); var indicatorViewModels = indicators.Select(MapToIndicatorViewModel);
return Ok(indicatorViewModels); return Ok(indicatorViewModels);
} }
@@ -127,7 +127,7 @@ public class ScenarioController : BaseController
int? cyclePeriods = null) int? cyclePeriods = null)
{ {
var user = await GetUser(); var user = await GetUser();
var indicator = _scenarioService.CreateIndicatorForUser( var indicator = await _scenarioService.CreateIndicatorForUser(
user, user,
indicatorType, indicatorType,
name, name,
@@ -153,7 +153,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> DeleteIndicator(string name) public async Task<ActionResult> DeleteIndicator(string name)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_scenarioService.DeleteIndicatorByUser(user, name)); return Ok(await _scenarioService.DeleteIndicatorByUser(user, name));
} }
// Update indicator // Update indicator
@@ -172,7 +172,7 @@ public class ScenarioController : BaseController
int? cyclePeriods = null) int? cyclePeriods = null)
{ {
var user = await GetUser(); var user = await GetUser();
return Ok(_scenarioService.UpdateIndicatorByUser( return Ok(await _scenarioService.UpdateIndicatorByUser(
user, user,
indicatorType, indicatorType,
name, name,

View File

@@ -1,19 +1,15 @@
using Microsoft.AspNetCore.Authorization; using Managing.Application.Abstractions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements; using Microsoft.AspNetCore.Authorization;
using Managing.Domain.Strategies; using Microsoft.AspNetCore.Mvc;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
/// <summary>
/// Controller for managing application settings and configurations.
/// Provides endpoints for setting up default configurations and resetting settings.
/// Requires authorization for access and produces JSON responses.
/// </summary>
[ApiController] [ApiController]
[Authorize] [Authorize]
[Route("[controller]")] [Route("[controller]")]
@@ -22,28 +18,58 @@ public class SettingsController : BaseController
{ {
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
/// <summary>
/// Initializes a new instance of the <see cref="SettingsController"/> class.
/// </summary>
/// <param name="settingsService">The service for managing application settings.</param>
/// <param name="userService">The service for user-related operations.</param>
public SettingsController(ISettingsService settingsService, IUserService userService) public SettingsController(ISettingsService settingsService, IUserService userService)
: base(userService) : base(userService)
{ {
_settingsService = settingsService; _settingsService = settingsService;
} }
/// <summary>
/// Sets up initial application settings.
/// </summary>
/// <returns>A result indicating if the setup was successful.</returns>
[HttpPost] [HttpPost]
public ActionResult SetupSettings() public async Task<ActionResult<bool>> SetupSettings()
{ {
return Ok(_settingsService.SetupSettings()); try
{
var result = await _settingsService.SetupSettings();
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, $"Error setting up settings: {ex.Message}");
}
} }
/// <summary>
/// Resets all application settings to their default values.
/// </summary>
/// <returns>A result indicating if the reset was successful.</returns>
[HttpDelete] [HttpDelete]
public async Task<ActionResult<bool>> ResetSettings() public async Task<ActionResult<bool>> ResetSettings()
{ {
return Ok(await _settingsService.ResetSettings()); try
{
var result = await _settingsService.ResetSettings();
return Ok(result);
}
catch (Exception ex)
{
return StatusCode(500, $"Error resetting settings: {ex.Message}");
}
} }
/// <summary> /// <summary>
/// Creates default configuration for backtesting including Money Management, Strategy, and Scenario /// Creates default configuration for backtesting including Money Management, Strategy, and Scenario
/// for the authenticated user.
/// </summary> /// </summary>
/// <returns>A result indicating if the default configuration was created successfully</returns> /// <returns>A result indicating if the default configuration was created successfully.</returns>
[HttpPost] [HttpPost]
[Route("create-default-config")] [Route("create-default-config")]
public async Task<ActionResult<bool>> CreateDefaultConfiguration() public async Task<ActionResult<bool>> CreateDefaultConfiguration()
@@ -52,9 +78,12 @@ public class SettingsController : BaseController
{ {
var user = await GetUser(); var user = await GetUser();
if (user == null) if (user == null)
return Unauthorized("User not found"); {
return Unauthorized("User not found or authentication failed");
}
return Ok(await _settingsService.CreateDefaultConfiguration(user)); var result = await _settingsService.CreateDefaultConfiguration(user);
return Ok(result);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -51,17 +51,6 @@ public class TradingController : BaseController
_moneyManagementService = moneyManagementService; _moneyManagementService = moneyManagementService;
} }
/// <summary>
/// Retrieves a list of positions based on the initiator type.
/// </summary>
/// <param name="positionInitiator">The initiator of the position (e.g., User, System).</param>
/// <returns>A list of positions.</returns>
[HttpGet("GetPositions")]
public async Task<ActionResult<List<Position>>> GetPositions(PositionInitiator positionInitiator)
{
var result = await _mediator.Send(new GetPositionsCommand(positionInitiator));
return Ok(result);
}
/// <summary> /// <summary>
/// Retrieves a specific trade by account name, ticker, and exchange order ID. /// Retrieves a specific trade by account name, ticker, and exchange order ID.
@@ -98,7 +87,7 @@ public class TradingController : BaseController
[HttpPost("ClosePosition")] [HttpPost("ClosePosition")]
public async Task<ActionResult<Position>> ClosePosition(string identifier) public async Task<ActionResult<Position>> ClosePosition(string identifier)
{ {
var position = _tradingService.GetPositionByIdentifier(identifier); var position = await _tradingService.GetPositionByIdentifierAsync(identifier);
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position)); var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
return Ok(result); return Ok(result);
} }

View File

@@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="8.1.0"/> <PackageReference Include="AspNetCore.HealthChecks.Npgsql" Version="8.1.0"/>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0"/> <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0"/>
<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"/>
@@ -52,8 +52,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Workers\"/>
</ItemGroup>
</Project> </Project>

View File

@@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
namespace Managing.Api.Models.Responses namespace Managing.Api.Models.Responses
@@ -11,56 +10,67 @@ namespace Managing.Api.Models.Responses
/// <summary> /// <summary>
/// Current status of the bot (Up, Down, etc.) /// Current status of the bot (Up, Down, etc.)
/// </summary> /// </summary>
[Required] public string Status { get; internal set; } [Required]
public string Status { get; internal set; }
/// <summary> /// <summary>
/// List of signals generated by the bot /// List of signals generated by the bot
/// </summary> /// </summary>
[Required] public List<Signal> Signals { get; internal set; } [Required]
public List<LightSignal> Signals { get; internal set; }
/// <summary> /// <summary>
/// List of positions opened by the bot /// List of positions opened by the bot
/// </summary> /// </summary>
[Required] public List<Position> Positions { get; internal set; } [Required]
public List<Position> Positions { get; internal set; }
/// <summary> /// <summary>
/// Candles used by the bot for analysis /// Candles used by the bot for analysis
/// </summary> /// </summary>
[Required] public List<Candle> Candles { get; internal set; } [Required]
public List<Candle> Candles { get; internal set; }
/// <summary> /// <summary>
/// Current win rate percentage /// Current win rate percentage
/// </summary> /// </summary>
[Required] public int WinRate { get; internal set; } [Required]
public int WinRate { get; internal set; }
/// <summary> /// <summary>
/// Current profit and loss /// Current profit and loss
/// </summary> /// </summary>
[Required] public decimal ProfitAndLoss { get; internal set; } [Required]
public decimal ProfitAndLoss { get; internal set; }
/// <summary> /// <summary>
/// Unique identifier for the bot /// Unique identifier for the bot
/// </summary> /// </summary>
[Required] public string Identifier { get; set; } [Required]
public string Identifier { get; set; }
/// <summary> /// <summary>
/// Agent name associated with the bot /// Agent name associated with the bot
/// </summary> /// </summary>
[Required] public string AgentName { get; set; } [Required]
public string AgentName { get; set; }
/// <summary> /// <summary>
/// The full trading bot configuration /// The full trading bot configuration
/// </summary> /// </summary>
[Required] public TradingBotConfig Config { get; internal set; } [Required]
public TradingBotConfig Config { get; internal set; }
/// <summary> /// <summary>
/// The time when the bot was created /// The time when the bot was created
/// </summary> /// </summary>
[Required] public DateTime CreateDate { get; internal set; } [Required]
public DateTime CreateDate { get; internal set; }
/// <summary> /// <summary>
/// The time when the bot was started /// The time when the bot was started
/// </summary> /// </summary>
[Required] public DateTime StartupTime { get; internal set; } [Required]
public DateTime StartupTime { get; internal set; }
} }
} }

View File

@@ -10,11 +10,12 @@ using Managing.Bootstrap;
using Managing.Common; using Managing.Common;
using Managing.Core.Middleawares; using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models; using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb; using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.MongoDb.Configurations; using Managing.Infrastructure.Databases.PostgreSql.Configurations;
using Managing.Infrastructure.Evm.Models.Privy; using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
@@ -71,7 +72,7 @@ builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks() builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]); .AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]);
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"]; var postgreSqlConnectionString = builder.Configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"]; var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"]; var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
@@ -87,9 +88,38 @@ builder.Services.AddHttpClient("GmxHealthCheck")
builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp => builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp =>
new Web3ProxyHealthCheck(sp.GetRequiredService<IHttpClientFactory>(), web3ProxyUrl)); new Web3ProxyHealthCheck(sp.GetRequiredService<IHttpClientFactory>(), web3ProxyUrl));
// Add PostgreSQL DbContext with improved concurrency and connection management
builder.Services.AddDbContext<ManagingDbContext>(options =>
{
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
{
// Configure connection pooling and timeout settings for better concurrency
npgsqlOptions.CommandTimeout(60); // Increase command timeout for complex queries
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
errorCodesToAdd: null);
});
// Enable detailed errors in development
if (builder.Environment.IsDevelopment())
{
options.EnableDetailedErrors();
options.EnableSensitiveDataLogging();
options.EnableThreadSafetyChecks(); // Enable thread safety checks in development
}
// Configure query tracking behavior for better performance
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Default to no tracking for better performance
// Enable service provider caching for better performance
options.EnableServiceProviderCaching();
// Enable connection resiliency for backtest and high-load scenarios
options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning); // Log warnings for connection issues
}, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation
// Add specific health checks for databases and other services // Add specific health checks for databases and other services
builder.Services.AddHealthChecks() builder.Services.AddHealthChecks()
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"]) .AddNpgSql(postgreSqlConnectionString, name: "postgresql", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"]) .AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"]) .AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"])
.AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"]) .AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"])
@@ -120,7 +150,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
}); });
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb)); builder.Services.Configure<PostgreSqlSettings>(builder.Configuration.GetSection(Constants.Databases.PostgreSql));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb)); builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy)); builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options => builder.Services.AddControllers().AddJsonOptions(options =>
@@ -209,25 +239,6 @@ if (builder.Configuration.GetValue<bool>("EnableBotManager", false))
// App // App
var app = builder.Build(); var app = builder.Build();
app.UseSerilogRequestLogging(); app.UseSerilogRequestLogging();
// Create MongoDB indexes on startup
try
{
var indexService = app.Services.GetRequiredService<IndexService>();
await indexService.CreateIndexesAsync();
}
catch (Exception ex)
{
// Log the error but don't fail the application startup
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes.");
}
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOpenApi(); app.UseOpenApi();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {

View File

@@ -1,21 +0,0 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Error",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200/"
},
"Sentry": {
"Debug": true,
"TracesSampleRate": 1.0,
"SendDefaultPii": true,
"DiagnosticLevel": "Debug"
}
}

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://influxdb:8086/", "Url": "http://influxdb:8086/",
"Organization": "", "Organization": "",

View File

@@ -1,10 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb",
"UserName": "admin",
"Password": "!MotdepasseFort11"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://influxdb:8086/", "Url": "http://influxdb:8086/",
"Organization": "managing-org", "Organization": "managing-org",

View File

@@ -1,13 +1,12 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "http://localhost:8086/", "Url": "http://localhost:8086/",
"Organization": "managing-org", "Organization": "managing-org",
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==" "Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
}, },
"PostgreSql": {
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
},
"Privy": { "Privy": {
"AppId": "cm6f47n1l003jx7mjwaembhup", "AppId": "cm6f47n1l003jx7mjwaembhup",
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF" "AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
@@ -39,5 +38,6 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"WorkerBotManager": true, "WorkerBotManager": true,
"WorkerBalancesTracking": true "WorkerBalancesTracking": false,
"WorkerNotifyBundleBacktest": true
} }

View File

@@ -1,7 +1,6 @@
{ {
"ManagingDatabase": { "PostgreSql": {
"ConnectionString": "mongodb://admin:vgRehYTdhghDR@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256", "ConnectionString": "Host=apps.prod.live;Port=5432;Database=managing;Username=postgres;Password=postgres"
"DatabaseName": "ManagingDb"
}, },
"InfluxDb": { "InfluxDb": {
"Url": "https://influx-db.apps.managing.live", "Url": "https://influx-db.apps.managing.live",

View File

@@ -1,7 +1,6 @@
{ {
"ManagingDatabase": { "PostgreSql": {
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256", "ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
"DatabaseName": "ManagingDb"
}, },
"InfluxDb": { "InfluxDb": {
"Url": "http://srv-captain--influx-db:8086/", "Url": "http://srv-captain--influx-db:8086/",

View File

@@ -1,8 +1,4 @@
{ {
"ManagingDatabase": {
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
"DatabaseName": "ManagingDb"
},
"InfluxDb": { "InfluxDb": {
"Url": "https://influx-db.apps.managing.live", "Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org", "Organization": "managing-org",
@@ -12,6 +8,9 @@
"AppId": "cm6f47n1l003jx7mjwaembhup", "AppId": "cm6f47n1l003jx7mjwaembhup",
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF" "AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
}, },
"PostgreSql": {
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
},
"Serilog": { "Serilog": {
"MinimumLevel": { "MinimumLevel": {
"Default": "Information", "Default": "Information",

View File

@@ -12,9 +12,8 @@
"Jwt": { "Jwt": {
"Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6" "Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6"
}, },
"ManagingDatabase": { "PostgreSql": {
"ConnectionString": "mongodb://managingdb", "ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
"DatabaseName": "ManagingDb"
}, },
"InfluxDb": { "InfluxDb": {
"Url": "http://influxdb:8086/", "Url": "http://influxdb:8086/",

View File

@@ -1,4 +1,4 @@
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users; using Managing.Domain.Users;
namespace Managing.Application.Abstractions namespace Managing.Application.Abstractions
@@ -8,8 +8,8 @@ namespace Managing.Application.Abstractions
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request); Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
Task<MoneyManagement> GetMoneyMangement(User user, string name); Task<MoneyManagement> GetMoneyMangement(User user, string name);
Task<MoneyManagement> GetMoneyMangement(string name); Task<MoneyManagement> GetMoneyMangement(string name);
IEnumerable<MoneyManagement> GetMoneyMangements(User user); Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user);
bool DeleteMoneyManagement(User user, string name); Task<bool> DeleteMoneyManagement(User user, string name);
bool DeleteMoneyManagements(User user); Task<bool> DeleteMoneyManagements(User user);
} }
} }

View File

@@ -0,0 +1 @@

View File

@@ -8,5 +8,5 @@ public interface IAccountRepository
Task<Account> GetAccountByKeyAsync(string key); Task<Account> GetAccountByKeyAsync(string key);
Task InsertAccountAsync(Account account); Task InsertAccountAsync(Account account);
void DeleteAccountByName(string name); void DeleteAccountByName(string name);
IEnumerable<Account> GetAccounts(); Task<IEnumerable<Account>> GetAccountsAsync();
} }

View File

@@ -7,25 +7,39 @@ public interface IBacktestRepository
{ {
void InsertBacktestForUser(User user, Backtest result); void InsertBacktestForUser(User user, Backtest result);
IEnumerable<Backtest> GetBacktestsByUser(User user); IEnumerable<Backtest> GetBacktestsByUser(User user);
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId); IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc"); int pageSize, string sortBy = "score", string sortOrder = "desc");
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId,
int page,
int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc"); int pageSize, string sortBy = "score", string sortOrder = "desc");
Backtest GetBacktestByIdForUser(User user, string id); Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page,
void DeleteBacktestByIdForUser(User user, string id); int pageSize, string sortBy = "score", string sortOrder = "desc");
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
Task DeleteBacktestByIdForUserAsync(User user, string id);
Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
void DeleteAllBacktestsForUser(User user); void DeleteAllBacktestsForUser(User user);
void DeleteBacktestsByRequestId(string requestId); Task DeleteBacktestsByRequestIdAsync(string requestId);
// Bundle backtest methods // Bundle backtest methods
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest); void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id); void DeleteBundleBacktestRequestByIdForUser(User user, string id);
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
} }

View File

@@ -5,7 +5,8 @@ namespace Managing.Application.Abstractions.Repositories;
public interface IBotRepository public interface IBotRepository
{ {
Task InsertBotAsync(BotBackup bot); Task InsertBotAsync(BotBackup bot);
IEnumerable<BotBackup> GetBots(); Task<IEnumerable<BotBackup>> GetBotsAsync();
Task UpdateBackupBot(BotBackup bot); Task UpdateBackupBot(BotBackup bot);
Task DeleteBotBackup(string botName); Task DeleteBotBackup(string botName);
Task<BotBackup?> GetBotByIdentifierAsync(string identifier);
} }

View File

@@ -34,7 +34,7 @@ public interface IGeneticRepository
/// Updates a genetic request /// Updates a genetic request
/// </summary> /// </summary>
/// <param name="geneticRequest">The genetic request to update</param> /// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest); Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
/// <summary> /// <summary>
/// Deletes a genetic request by ID for a user /// Deletes a genetic request by ID for a user
@@ -50,8 +50,9 @@ public interface IGeneticRepository
void DeleteAllGeneticRequestsForUser(User user); void DeleteAllGeneticRequestsForUser(User user);
/// <summary> /// <summary>
/// Gets all pending genetic requests across all users /// Gets all genetic requests by status across all users
/// </summary> /// </summary>
/// <returns>Collection of pending genetic requests</returns> /// <param name="status">The status to filter by</param>
IEnumerable<GeneticRequest> GetPendingGeneticRequests(); /// <returns>Collection of genetic requests</returns>
Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status);
} }

View File

@@ -6,15 +6,15 @@ namespace Managing.Application.Abstractions.Repositories;
public interface ISettingsRepository public interface ISettingsRepository
{ {
Task<MoneyManagement> GetMoneyManagement(string name); Task<MoneyManagement> GetMoneyManagement(string name);
Task InsertMoneyManagement(MoneyManagement request); Task InsertMoneyManagement(LightMoneyManagement request, User user);
void UpdateMoneyManagement(MoneyManagement moneyManagement); Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user);
IEnumerable<MoneyManagement> GetMoneyManagements(); Task<IEnumerable<MoneyManagement>> GetMoneyManagementsAsync();
void DeleteMoneyManagement(string name); Task DeleteMoneyManagementAsync(string name);
void DeleteMoneyManagements(); Task DeleteMoneyManagementsAsync();
// User-specific operations (these should eventually replace the above methods) // User-specific operations (these should eventually replace the above methods)
Task<MoneyManagement> GetMoneyManagementByUser(User user, string name); Task<MoneyManagement> GetMoneyManagementByUser(User user, string name);
IEnumerable<MoneyManagement> GetMoneyManagementsByUser(User user); Task<IEnumerable<MoneyManagement>> GetMoneyManagementsByUserAsync(User user);
void DeleteMoneyManagementByUser(User user, string name); Task DeleteMoneyManagementByUserAsync(User user, string name);
void DeleteMoneyManagementsByUser(User user); Task DeleteMoneyManagementsByUserAsync(User user);
} }

View File

@@ -6,19 +6,26 @@ public interface IStatisticRepository
{ {
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker); Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date); IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
Task<IList<TopVolumeTicker>> GetTopVolumeTickersAsync(DateTime date);
Task SaveSpotligthtOverview(SpotlightOverview overview); Task SaveSpotligthtOverview(SpotlightOverview overview);
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date); IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
Task<IList<SpotlightOverview>> GetSpotlightOverviewsAsync(DateTime date);
void UpdateSpotlightOverview(SpotlightOverview overview); void UpdateSpotlightOverview(SpotlightOverview overview);
List<Trader> GetBestTraders(); Task UpdateSpotlightOverviewAsync(SpotlightOverview overview);
void UpdateBestTrader(Trader trader);
Task<List<Trader>> GetBestTradersAsync();
Task UpdateBestTraderAsync(Trader trader);
Task InsertBestTrader(Trader trader); Task InsertBestTrader(Trader trader);
Task RemoveBestTrader(Trader trader); Task RemoveBestTrader(Trader trader);
List<Trader> GetBadTraders();
void UpdateBadTrader(Trader trader); Task<List<Trader>> GetBadTradersAsync();
Task UpdateBadTraderAsync(Trader trader);
Task InsertBadTrader(Trader trader); Task InsertBadTrader(Trader trader);
Task RemoveBadTrader(Trader trader); Task RemoveBadTrader(Trader trader);
List<FundingRate> GetFundingRates();
Task<List<FundingRate>> GetFundingRatesAsync();
Task RemoveFundingRate(FundingRate oldRate); Task RemoveFundingRate(FundingRate oldRate);
Task InsertFundingRate(FundingRate newRate); Task InsertFundingRate(FundingRate newRate);
void UpdateFundingRate(FundingRate oldRate, FundingRate newRate); Task UpdateFundingRateAsync(FundingRate oldRate, FundingRate newRate);
} }

View File

@@ -8,27 +8,23 @@ namespace Managing.Application.Abstractions.Repositories;
public interface ITradingRepository public interface ITradingRepository
{ {
Scenario GetScenarioByName(string scenario); Task<Scenario> GetScenarioByNameAsync(string scenario);
void InsertSignal(Signal signal); Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user);
IEnumerable<Signal> GetSignalsByUser(User user); Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null);
Signal GetSignalByIdentifier(string identifier, User user = null); Task InsertPositionAsync(Position position);
void InsertPosition(Position position); Task UpdatePositionAsync(Position position);
void UpdatePosition(Position position); Task<Indicator> GetStrategyByNameAsync(string strategy);
Indicator GetStrategyByName(string strategy); Task InsertScenarioAsync(Scenario scenario);
void InsertScenario(Scenario scenario); Task InsertStrategyAsync(Indicator indicator);
void InsertStrategy(Indicator indicator); Task<IEnumerable<Scenario>> GetScenariosAsync();
IEnumerable<Scenario> GetScenarios(); Task<IEnumerable<Indicator>> GetStrategiesAsync();
IEnumerable<Indicator> GetIndicators(); Task<IEnumerable<Indicator>> GetIndicatorsAsync();
void DeleteScenario(string name); Task DeleteScenarioAsync(string name);
void DeleteIndicator(string name); Task DeleteIndicatorAsync(string name);
void DeleteScenarios(); Task<Position> GetPositionByIdentifierAsync(string identifier);
void DeleteIndicators(); Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
Position GetPositionByIdentifier(string identifier); Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus); Task UpdateScenarioAsync(Scenario scenario);
Fee GetFee(TradingExchanges exchange); Task UpdateStrategyAsync(Indicator indicator);
void InsertFee(Fee fee);
void UpdateFee(Fee fee);
void UpdateScenario(Scenario scenario);
void UpdateStrategy(Indicator indicator);
} }

View File

@@ -8,7 +8,7 @@ public interface IWorkerRepository
Task DisableWorker(Enums.WorkerType workerType); Task DisableWorker(Enums.WorkerType workerType);
Task EnableWorker(Enums.WorkerType workerType); Task EnableWorker(Enums.WorkerType workerType);
Task<Worker> GetWorkerAsync(Enums.WorkerType workerType); Task<Worker> GetWorkerAsync(Enums.WorkerType workerType);
IEnumerable<Worker> GetWorkers(); Task<IEnumerable<Worker>> GetWorkers();
Task InsertWorker(Worker worker); Task InsertWorker(Worker worker);
Task UpdateWorker(Enums.WorkerType workerType, int executionCount); Task UpdateWorker(Enums.WorkerType workerType, int executionCount);
} }

View File

@@ -9,15 +9,29 @@ public interface IAccountService
Task<Account> CreateAccount(User user, Account account); Task<Account> CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name); bool DeleteAccount(User user, string name);
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true); IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance); Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true);
Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance);
Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance);
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance); Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance); public Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance); public Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
/// <summary>
/// Gets an account by name directly from the repository.
/// </summary>
/// <param name="accountName">The name of the account to find</param>
/// <param name="hideSecrets">Whether to hide sensitive information</param>
/// <param name="getBalance">Whether to fetch the current balance</param>
/// <returns>The found account or null if not found</returns>
Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false);
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true); IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName); Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5); double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null); Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker,
decimal amount, int? chainId = null);
} }

View File

@@ -50,25 +50,34 @@ namespace Managing.Application.Abstractions.Services
object metadata = null); object metadata = null);
// Additional methods for backtest management // Additional methods for backtest management
bool DeleteBacktest(string id); Task<bool> DeleteBacktestAsync(string id);
bool DeleteBacktests(); bool DeleteBacktests();
IEnumerable<Backtest> GetBacktestsByUser(User user); IEnumerable<Backtest> GetBacktestsByUser(User user);
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId); IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
Backtest GetBacktestByIdForUser(User user, string id); Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
bool DeleteBacktestByUser(User user, string id); Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids); Task<bool> DeleteBacktestByUserAsync(User user, string id);
Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
bool DeleteBacktestsByUser(User user); bool DeleteBacktestsByUser(User user);
bool DeleteBacktestsByRequestId(string requestId); Task<bool> DeleteBacktestsByRequestIdAsync(string requestId);
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc"); (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
// Bundle backtest methods // Bundle backtest methods
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest); void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id); void DeleteBundleBacktestRequestByIdForUser(User user, string id);
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
} }

View File

@@ -25,7 +25,7 @@ public interface IExchangeService
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false); Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false); Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
decimal GetPrice(Account account, Ticker ticker, DateTime date); Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
Task<Trade> GetTrade(Account account, string order, Ticker ticker); Task<Trade> GetTrade(Account account, string order, Ticker ticker);
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval, Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval,
@@ -54,7 +54,7 @@ public interface IExchangeService
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
Timeframe timeframe, DateTime endDate); Timeframe timeframe, DateTime endDate);
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction); Task<decimal> GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
Orderbook GetOrderbook(Account account, Ticker ticker); Orderbook GetOrderbook(Account account, Ticker ticker);
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage,

View File

@@ -64,7 +64,7 @@ public interface IGeneticService
/// Updates a genetic request /// Updates a genetic request
/// </summary> /// </summary>
/// <param name="geneticRequest">The genetic request to update</param> /// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest); Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
/// <summary> /// <summary>
/// Deletes a genetic request by ID for a user /// Deletes a genetic request by ID for a user
@@ -74,10 +74,11 @@ public interface IGeneticService
void DeleteGeneticRequestByIdForUser(User user, string id); void DeleteGeneticRequestByIdForUser(User user, string id);
/// <summary> /// <summary>
/// Gets all pending genetic requests across all users /// Gets all genetic requests by status across all users
/// </summary> /// </summary>
/// <returns>Collection of pending genetic requests</returns> /// <param name="status">The status to filter by</param>
IEnumerable<GeneticRequest> GetPendingGeneticRequests(); /// <returns>Collection of genetic requests</returns>
Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status);
/// <summary> /// <summary>
/// Runs the genetic algorithm for a specific request /// Runs the genetic algorithm for a specific request

View File

@@ -13,9 +13,13 @@ public interface IStatisticService
int pageSize = 10); int pageSize = 10);
List<Trader> GetBadTraders(); List<Trader> GetBadTraders();
Task<List<Trader>> GetBadTradersAsync();
List<Trader> GetBestTraders(); List<Trader> GetBestTraders();
SpotlightOverview GetLastSpotlight(DateTime dateTime); Task<List<Trader>> GetBestTradersAsync();
Task<SpotlightOverview> GetLastSpotlight(DateTime dateTime);
Task<SpotlightOverview> GetLastSpotlightAsync(DateTime dateTime);
IList<TopVolumeTicker> GetLastTopVolumeTicker(); IList<TopVolumeTicker> GetLastTopVolumeTicker();
Task<IList<TopVolumeTicker>> GetLastTopVolumeTickerAsync();
Task<List<Trade>> GetLeadboardPositons(); Task<List<Trade>> GetLeadboardPositons();
Task<IList<Enums.Ticker>> GetTickers(); Task<IList<Enums.Ticker>> GetTickers();
Task UpdateLeaderboard(); Task UpdateLeaderboard();

View File

@@ -1,6 +1,4 @@
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Synth.Models; using Managing.Domain.Synth.Models;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -70,7 +68,7 @@ public interface ISynthPredictionService
/// <param name="isBacktest">Whether this is a backtest</param> /// <param name="isBacktest">Whether this is a backtest</param>
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param> /// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
/// <returns>Comprehensive signal validation result including confidence, probabilities, and risk analysis</returns> /// <returns>Comprehensive signal validation result including confidence, probabilities, and risk analysis</returns>
Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice, Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null); TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null);
/// <summary> /// <summary>
@@ -105,5 +103,5 @@ public interface ISynthPredictionService
/// <param name="direction">Position direction</param> /// <param name="direction">Position direction</param>
/// <param name="moneyManagement">Money management settings</param> /// <param name="moneyManagement">Money management settings</param>
/// <returns>Estimated liquidation price</returns> /// <returns>Estimated liquidation price</returns>
decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, MoneyManagement moneyManagement); decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, LightMoneyManagement moneyManagement);
} }

View File

@@ -14,36 +14,28 @@ namespace Managing.Application.Abstractions.Services;
public interface ITradingService public interface ITradingService
{ {
Scenario GetScenarioByName(string scenario); Task<Scenario> GetScenarioByNameAsync(string scenario);
void InsertSignal(Signal signal); Task InsertPositionAsync(Position position);
void InsertPosition(Position position); Task UpdatePositionAsync(Position position);
void UpdatePosition(Position position); Task<Indicator> GetStrategyByNameAsync(string strategy);
Indicator GetStrategyByName(string strategy); Task InsertScenarioAsync(Scenario scenario);
void InsertScenario(Scenario scenario); Task InsertStrategyAsync(Indicator indicator);
void InsertStrategy(Indicator indicator); Task<IEnumerable<Scenario>> GetScenariosAsync();
IEnumerable<Scenario> GetScenarios(); Task<IEnumerable<Indicator>> GetStrategiesAsync();
IEnumerable<Indicator> GetStrategies(); Task DeleteScenarioAsync(string name);
void DeleteScenario(string name); Task DeleteStrategyAsync(string name);
void DeleteStrategy(string name); Task<Position> GetPositionByIdentifierAsync(string identifier);
void DeleteScenarios();
void DeleteStrategies();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositions();
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
Task<Position> ManagePosition(Account account, Position position); Task<Position> ManagePosition(Account account, Position position);
void UpdateFee(TradingExchanges evm);
decimal GetFee(Account account, bool isForPaperTrading = false);
Task WatchTrader(); Task WatchTrader();
IEnumerable<Trader> GetTradersWatch(); Task<IEnumerable<Trader>> GetTradersWatch();
void UpdateDeltaNeutralOpportunities(); Task UpdateScenarioAsync(Scenario scenario);
void UpdateScenario(Scenario scenario); Task UpdateStrategyAsync(Indicator indicator);
void UpdateStrategy(Indicator indicator);
Task<IEnumerable<Position>> GetBrokerPositions(Account account); Task<IEnumerable<Position>> GetBrokerPositions(Account account);
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress); Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
// Synth API integration methods // Synth API integration methods
Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice, Task<SignalValidationResult> ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, TradingBotConfig botConfig,
bool isBacktest); bool isBacktest);
@@ -59,7 +51,7 @@ public interface ITradingService
/// <param name="scenario">The scenario containing indicators.</param> /// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param> /// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns> /// <returns>A dictionary of indicator types to their calculated values.</returns>
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync( Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
Scenario scenario, Scenario scenario,
List<Candle> candles); List<Candle> candles);
} }

View File

@@ -9,5 +9,5 @@ public interface IUserService
Task<User> UpdateAgentName(User user, string agentName); Task<User> UpdateAgentName(User user, string agentName);
Task<User> UpdateAvatarUrl(User user, string avatarUrl); Task<User> UpdateAvatarUrl(User user, string avatarUrl);
Task<User> UpdateTelegramChannel(User user, string telegramChannel); Task<User> UpdateTelegramChannel(User user, string telegramChannel);
User GetUser(string name); Task<User> GetUser(string name);
} }

View File

@@ -5,12 +5,15 @@ 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.Base; using Managing.Application.Bots.Base;
using Managing.Application.Hubs;
using Managing.Application.ManageBot;
using Managing.Core; using Managing.Core;
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.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Microsoft.AspNetCore.SignalR;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xunit; using Xunit;
@@ -35,6 +38,8 @@ namespace Managing.Application.Tests
var scenarioService = new Mock<IScenarioService>().Object; var scenarioService = new Mock<IScenarioService>().Object;
var messengerService = new Mock<IMessengerService>().Object; var messengerService = new Mock<IMessengerService>().Object;
var kaigenService = new Mock<IKaigenService>().Object; var kaigenService = new Mock<IKaigenService>().Object;
var backupBotService = new Mock<IBackupBotService>().Object;
var hubContext = new Mock<IHubContext<BacktestHub>>().Object;
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
var botService = new Mock<IBotService>().Object; var botService = new Mock<IBotService>().Object;
@@ -44,9 +49,9 @@ namespace Managing.Application.Tests
discordService, discordService,
_accountService.Object, _accountService.Object,
_tradingService.Object, _tradingService.Object,
botService); botService, backupBotService);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger, _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
scenarioService, _accountService.Object, messengerService, kaigenService); scenarioService, _accountService.Object, messengerService, kaigenService, hubContext);
_elapsedTimes = new List<double>(); _elapsedTimes = new List<double>();
// Initialize cross-platform file paths // Initialize cross-platform file paths

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Signals; using Managing.Domain.Strategies.Signals;
using Managing.Domain.Strategies.Trends; using Managing.Domain.Strategies.Trends;
using Xunit; using Xunit;
@@ -26,7 +25,7 @@ namespace Managing.Application.Tests
// Arrange // Arrange
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5); var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -39,7 +38,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(rsiStrategy.Signals); resultSignal.AddRange(rsiStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }
@@ -60,7 +59,7 @@ namespace Managing.Application.Tests
var account = GetAccount(exchange); var account = GetAccount(exchange);
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5); var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result; var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -73,7 +72,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(rsiStrategy.Signals); resultSignal.AddRange(rsiStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
} }
@@ -87,7 +86,7 @@ namespace Managing.Application.Tests
var account = GetAccount(exchange); var account = GetAccount(exchange);
var rsiStrategy = new MacdCrossIndicator("unittest", 12, 26, 9); var rsiStrategy = new MacdCrossIndicator("unittest", 12, 26, 9);
var candles = await _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe); var candles = await _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe);
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -100,7 +99,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(rsiStrategy.Signals); resultSignal.AddRange(rsiStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }
@@ -114,7 +113,7 @@ namespace Managing.Application.Tests
var account = GetAccount(exchange); var account = GetAccount(exchange);
var superTrendStrategy = new SuperTrendIndicator("unittest", 10, 3); var superTrendStrategy = new SuperTrendIndicator("unittest", 10, 3);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -127,7 +126,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(superTrendStrategy.Signals); resultSignal.AddRange(superTrendStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }
@@ -142,7 +141,7 @@ namespace Managing.Application.Tests
var chandelierExitStrategy = new ChandelierExitIndicator("unittest", 22, 3); var chandelierExitStrategy = new ChandelierExitIndicator("unittest", 22, 3);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe, false) var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe, false)
.Result; .Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -155,7 +154,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(chandelierExitStrategy.Signals); resultSignal.AddRange(chandelierExitStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }
@@ -169,7 +168,7 @@ namespace Managing.Application.Tests
var account = GetAccount(exchange); var account = GetAccount(exchange);
var emaTrendSrategy = new EmaTrendIndicator("unittest", 200); var emaTrendSrategy = new EmaTrendIndicator("unittest", 200);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// Act // Act
foreach (var candle in candles) foreach (var candle in candles)
@@ -182,7 +181,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(emaTrendSrategy.Signals); resultSignal.AddRange(emaTrendSrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }
@@ -197,7 +196,7 @@ namespace Managing.Application.Tests
var account = GetAccount(exchange); var account = GetAccount(exchange);
var stochRsiStrategy = new StochRsiTrendIndicator("unittest", 14, 14, 3, 1); var stochRsiStrategy = new StochRsiTrendIndicator("unittest", 14, 14, 3, 1);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result; var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>(); var resultSignal = new List<LightSignal>();
// var json = JsonConvert.SerializeObject(candles); // var json = JsonConvert.SerializeObject(candles);
// File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-candles.json", json); // File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-candles.json", json);
@@ -214,7 +213,7 @@ namespace Managing.Application.Tests
resultSignal.AddRange(stochRsiStrategy.Signals); resultSignal.AddRange(stochRsiStrategy.Signals);
// Assert // Assert
Assert.IsType<List<Signal>>(resultSignal); Assert.IsType<List<LightSignal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long); Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
} }

View File

@@ -51,7 +51,8 @@ public class PositionTests : BaseTests
Open = openTrade Open = openTrade
}; };
var command = new ClosePositionCommand(position); var command = new ClosePositionCommand(position);
_ = _tradingService.Setup(m => m.GetPositionByIdentifier(It.IsAny<string>())).Returns(position); _ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny<string>())).ReturnsAsync(position);
_ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny<string>())).ReturnsAsync(position);
var handler = new ClosePositionCommandHandler( var handler = new ClosePositionCommandHandler(
_exchangeService, _exchangeService,

View File

@@ -8,7 +8,7 @@ public interface IWorkerService
Task DisableWorker(WorkerType workerType); Task DisableWorker(WorkerType workerType);
Task EnableWorker(WorkerType workerType); Task EnableWorker(WorkerType workerType);
Task<Worker> GetWorker(WorkerType workerType); Task<Worker> GetWorker(WorkerType workerType);
IEnumerable<Worker> GetWorkers(); Task<IEnumerable<Worker>> GetWorkers();
Task InsertWorker(WorkerType workerType, TimeSpan delay); Task InsertWorker(WorkerType workerType, TimeSpan delay);
Task<bool> ToggleWorker(WorkerType workerType); Task<bool> ToggleWorker(WorkerType workerType);
Task UpdateWorker(WorkerType workerType, int executionCount); Task UpdateWorker(WorkerType workerType, int executionCount);

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -7,22 +8,22 @@ namespace Managing.Application.Workers;
public abstract class BaseWorker<T> : BackgroundService where T : class public abstract class BaseWorker<T> : BackgroundService where T : class
{ {
private readonly IServiceProvider _serviceProvider;
private readonly WorkerType _workerType; private readonly WorkerType _workerType;
protected readonly ILogger<T> _logger; protected readonly ILogger<T> _logger;
protected readonly TimeSpan _delay; protected readonly TimeSpan _delay;
private readonly IWorkerService _workerService;
private int _executionCount; private int _executionCount;
protected BaseWorker( protected BaseWorker(
WorkerType workerType, WorkerType workerType,
ILogger<T> logger, ILogger<T> logger,
TimeSpan timeSpan, TimeSpan timeSpan,
IWorkerService workerService) IServiceProvider serviceProvider)
{ {
_workerType = workerType; _workerType = workerType;
_logger = logger; _logger = logger;
_delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan; _delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan;
_workerService = workerService; _serviceProvider = serviceProvider;
_executionCount = 0; _executionCount = 0;
} }
@@ -31,28 +32,35 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
try try
{ {
_logger.LogInformation($"[{_workerType}] Starting"); _logger.LogInformation($"[{_workerType}] Starting");
var worker = await _workerService.GetWorker(_workerType); using (var scope = _serviceProvider.CreateScope())
{
var workerService = scope.ServiceProvider.GetRequiredService<IWorkerService>();
var worker = await workerService.GetWorker(_workerType);
if (worker == null) if (worker == null)
{ {
await _workerService.InsertWorker(_workerType, _delay); await workerService.InsertWorker(_workerType, _delay);
} }
else else
{ {
_logger.LogInformation( _logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
$"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}"); _executionCount = worker.ExecutionCount;
_executionCount = worker.ExecutionCount; }
} }
cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping")); cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping"));
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
worker = await _workerService.GetWorker(_workerType); using (var scope = _serviceProvider.CreateScope())
{
var workerService = scope.ServiceProvider.GetRequiredService<IWorkerService>();
var worker = await workerService.GetWorker(_workerType);
await Run(cancellationToken); await Run(cancellationToken);
_executionCount++; _executionCount++;
await _workerService.UpdateWorker(_workerType, _executionCount); await workerService.UpdateWorker(_workerType, _executionCount);
}
_logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}"); _logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}");
await Task.Delay(_delay); await Task.Delay(_delay);
} }

View File

@@ -1,11 +1,11 @@
using System.Text.Json; using System.Text.Json;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -16,22 +16,20 @@ namespace Managing.Application.Workers;
/// </summary> /// </summary>
public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker> public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
{ {
// Removed direct repository usage for bundle requests private readonly IServiceProvider _serviceProvider;
private readonly IBacktester _backtester;
private readonly IMessengerService _messengerService; private readonly IMessengerService _messengerService;
private static readonly WorkerType _workerType = WorkerType.BundleBacktest; private static readonly WorkerType _workerType = WorkerType.BundleBacktest;
public BundleBacktestWorker( public BundleBacktestWorker(
IBacktester backtester, IServiceProvider serviceProvider,
IMessengerService messengerService, IMessengerService messengerService,
ILogger<BundleBacktestWorker> logger, ILogger<BundleBacktestWorker> logger) : base(
IWorkerService workerService) : base(
_workerType, _workerType,
logger, logger,
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1),
workerService) serviceProvider)
{ {
_backtester = backtester; _serviceProvider = serviceProvider;
_messengerService = messengerService; _messengerService = messengerService;
} }
@@ -42,8 +40,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
var processingTasks = new List<Task>(); var processingTasks = new List<Task>();
try try
{ {
// Create a new service scope to get fresh instances of services with scoped DbContext
using var scope = _serviceProvider.CreateScope();
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
// Get pending bundle backtest requests // Get pending bundle backtest requests
var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending); var pendingRequests =
await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending);
foreach (var bundleRequest in pendingRequests) foreach (var bundleRequest in pendingRequests)
{ {
@@ -64,9 +67,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
}, cancellationToken); }, cancellationToken);
processingTasks.Add(task); processingTasks.Add(task);
} }
await Task.WhenAll(processingTasks); await Task.WhenAll(processingTasks);
await RetryUnfinishedBacktestsInFailedBundles(cancellationToken); await RetryUnfinishedBacktestsInFailedBundles(backtester, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -77,13 +81,17 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
private async Task ProcessBundleRequest(BundleBacktestRequest bundleRequest, CancellationToken cancellationToken) private async Task ProcessBundleRequest(BundleBacktestRequest bundleRequest, CancellationToken cancellationToken)
{ {
// Create a new service scope for this task to avoid DbContext concurrency issues
using var scope = _serviceProvider.CreateScope();
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
try try
{ {
_logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId); _logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);
// Update status to running // Update status to running
bundleRequest.Status = BundleBacktestRequestStatus.Running; bundleRequest.Status = BundleBacktestRequestStatus.Running;
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
// Deserialize the backtest requests as strongly-typed objects // Deserialize the backtest requests as strongly-typed objects
var backtestRequests = var backtestRequests =
@@ -105,10 +113,11 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
var runBacktestRequest = backtestRequests[i]; var runBacktestRequest = backtestRequests[i];
// Update current backtest being processed // Update current backtest being processed
bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}"; bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}";
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
// Run the backtest directly with the strongly-typed request // Run the backtest directly with the strongly-typed request
var backtestId = await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken); var backtestId = await RunSingleBacktest(backtester, runBacktestRequest, bundleRequest, i,
cancellationToken);
if (!string.IsNullOrEmpty(backtestId)) if (!string.IsNullOrEmpty(backtestId))
{ {
bundleRequest.Results.Add(backtestId); bundleRequest.Results.Add(backtestId);
@@ -116,7 +125,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
// Update progress // Update progress
bundleRequest.CompletedBacktests++; bundleRequest.CompletedBacktests++;
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}", _logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
i + 1, bundleRequest.RequestId); i + 1, bundleRequest.RequestId);
@@ -126,20 +135,16 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}", _logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
i + 1, bundleRequest.RequestId); i + 1, bundleRequest.RequestId);
bundleRequest.FailedBacktests++; bundleRequest.FailedBacktests++;
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
} }
} }
// Update final status // Update final status and send notifications
if (bundleRequest.FailedBacktests == 0) if (bundleRequest.FailedBacktests == 0)
{ {
bundleRequest.Status = BundleBacktestRequestStatus.Completed; bundleRequest.Status = BundleBacktestRequestStatus.Completed;
// Send Telegram message to the user's channelId // Send Telegram message to the user's channelId
if (bundleRequest.User?.TelegramChannel != null) await NotifyUser(bundleRequest);
{
var message = $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) is completed.";
await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
}
} }
else if (bundleRequest.CompletedBacktests == 0) else if (bundleRequest.CompletedBacktests == 0)
{ {
@@ -150,11 +155,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
{ {
bundleRequest.Status = BundleBacktestRequestStatus.Completed; bundleRequest.Status = BundleBacktestRequestStatus.Completed;
bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed"; bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed";
// Send Telegram message to the user's channelId even with partial failures
await NotifyUser(bundleRequest);
} }
bundleRequest.CompletedAt = DateTime.UtcNow; bundleRequest.CompletedAt = DateTime.UtcNow;
bundleRequest.CurrentBacktest = null; bundleRequest.CurrentBacktest = null;
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}", _logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
bundleRequest.RequestId, bundleRequest.Status); bundleRequest.RequestId, bundleRequest.Status);
@@ -166,12 +173,22 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
bundleRequest.Status = BundleBacktestRequestStatus.Failed; bundleRequest.Status = BundleBacktestRequestStatus.Failed;
bundleRequest.ErrorMessage = ex.Message; bundleRequest.ErrorMessage = ex.Message;
bundleRequest.CompletedAt = DateTime.UtcNow; bundleRequest.CompletedAt = DateTime.UtcNow;
_backtester.UpdateBundleBacktestRequest(bundleRequest); await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
}
}
private async Task NotifyUser(BundleBacktestRequest bundleRequest)
{
if (bundleRequest.User?.TelegramChannel != null)
{
var message =
$"⚠️ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed with {bundleRequest.FailedBacktests} failed backtests.";
await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
} }
} }
// Change RunSingleBacktest to accept RunBacktestRequest directly // Change RunSingleBacktest to accept RunBacktestRequest directly
private async Task<string> RunSingleBacktest(RunBacktestRequest runBacktestRequest, private async Task<string> RunSingleBacktest(IBacktester backtester, RunBacktestRequest runBacktestRequest,
BundleBacktestRequest bundleRequest, BundleBacktestRequest bundleRequest,
int index, CancellationToken cancellationToken) int index, CancellationToken cancellationToken)
{ {
@@ -259,7 +276,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
}; };
// Run the backtest (no user context) // Run the backtest (no user context)
var result = await _backtester.RunTradingBotBacktest( var result = await backtester.RunTradingBotBacktest(
backtestConfig, backtestConfig,
runBacktestRequest.StartDate, runBacktestRequest.StartDate,
runBacktestRequest.EndDate, runBacktestRequest.EndDate,
@@ -275,9 +292,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
return result.Id; return result.Id;
} }
private async Task RetryUnfinishedBacktestsInFailedBundles(CancellationToken cancellationToken) private async Task RetryUnfinishedBacktestsInFailedBundles(IBacktester backtester,
CancellationToken cancellationToken)
{ {
var failedBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Failed); var failedBundles = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Failed);
foreach (var failedBundle in failedBundles) foreach (var failedBundle in failedBundles)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@@ -292,20 +310,39 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
.Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson); .Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson);
if (originalRequests == null) continue; if (originalRequests == null) continue;
for (int i = 0; i < originalRequests.Count; i++) for (int i = failedBundle.CompletedBacktests; i < originalRequests.Count; i++)
{ {
var expectedId = /* logic to compute expected backtest id for this request */ string.Empty; var expectedId = /* logic to compute expected backtest id for this request */ string.Empty;
// If this backtest was not run or did not succeed, re-run it // If this backtest was not run or did not succeed, re-run it
if (!succeededIds.Contains(expectedId)) if (!succeededIds.Contains(expectedId))
{ {
var backtestId = await RunSingleBacktest(originalRequests[i], failedBundle, i, cancellationToken); var backtestId = await RunSingleBacktest(backtester, originalRequests[i], failedBundle, i,
cancellationToken);
if (!string.IsNullOrEmpty(backtestId)) if (!string.IsNullOrEmpty(backtestId))
{ {
failedBundle.Results?.Add(backtestId); failedBundle.Results?.Add(backtestId);
_backtester.UpdateBundleBacktestRequest(failedBundle); failedBundle.CompletedBacktests++;
await backtester.UpdateBundleBacktestRequestAsync(failedBundle);
} }
} }
} }
// If all backtests succeeded, update the bundle status
if (failedBundle.CompletedBacktests == originalRequests.Count)
{
failedBundle.Status = BundleBacktestRequestStatus.Completed;
failedBundle.ErrorMessage = null; // Clear any previous error
failedBundle.CompletedAt = DateTime.UtcNow;
await backtester.UpdateBundleBacktestRequestAsync(failedBundle);
// Notify user about successful retry
await NotifyUser(failedBundle);
}
else
{
_logger.LogWarning("Bundle {RequestId} still has unfinished backtests after retry",
failedBundle.RequestId);
}
} }
} }
} }

View File

@@ -1,30 +0,0 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Workers;
public class FeeWorker : BaseWorker<FeeWorker>
{
private readonly ITradingService _tradingService;
private static readonly WorkerType _workerType = WorkerType.Fee;
public FeeWorker(
ILogger<FeeWorker> logger,
ITradingService tradingService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(12),
workerService
)
{
_tradingService = tradingService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
_tradingService.UpdateFee(TradingExchanges.Evm);
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Common; using Managing.Common;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -11,12 +10,12 @@ public class FundingRatesWatcher : BaseWorker<FundingRatesWatcher>
public FundingRatesWatcher( public FundingRatesWatcher(
ILogger<FundingRatesWatcher> logger, ILogger<FundingRatesWatcher> logger,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
Enums.WorkerType.FundingRatesWatcher, Enums.WorkerType.FundingRatesWatcher,
logger, logger,
TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30),
workerService serviceProvider
) )
{ {
_statisticService = statisticService; _statisticService = statisticService;

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions; using Managing.Core;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -11,18 +12,15 @@ namespace Managing.Application.Workers;
/// </summary> /// </summary>
public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker> public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
{ {
private readonly IGeneticService _geneticService; private readonly IServiceScopeFactory _scopeFactory;
private readonly IBacktester _backtester;
public GeneticAlgorithmWorker( public GeneticAlgorithmWorker(
ILogger<GeneticAlgorithmWorker> logger, ILogger<GeneticAlgorithmWorker> logger,
IWorkerService workerService, IServiceProvider serviceProvider,
IGeneticService geneticService, IServiceScopeFactory scopeFactory)
IBacktester backtester) : base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), serviceProvider)
: base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), workerService)
{ {
_geneticService = geneticService; _scopeFactory = scopeFactory;
_backtester = backtester;
} }
protected override async Task Run(CancellationToken cancellationToken) protected override async Task Run(CancellationToken cancellationToken)
@@ -48,8 +46,9 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
{ {
try try
{ {
// Get pending genetic requests from the repository // Get pending genetic requests from the repository using scoped service
var pendingRequests = _geneticService.GetPendingGeneticRequests(); var pendingRequests = await ServiceScopeHelpers.WithScopedService<IGeneticService, IEnumerable<GeneticRequest>>(_scopeFactory,
async geneticService => await geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Pending));
if (!pendingRequests.Any()) if (!pendingRequests.Any())
{ {
@@ -65,21 +64,24 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
{ {
_logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId); _logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId);
// Update status to Running // Update status to Running using scoped service
request.Status = GeneticRequestStatus.Running; request.Status = GeneticRequestStatus.Running;
_geneticService.UpdateGeneticRequest(request); await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
// Run genetic algorithm using the service // Run genetic algorithm using the service
var results = await _geneticService.RunGeneticAlgorithm(request, cancellationToken); var results = await ServiceScopeHelpers.WithScopedServices<IGeneticService, IBacktester, GeneticAlgorithmResult>(_scopeFactory,
async (geneticService, backtester) => await geneticService.RunGeneticAlgorithm(request, cancellationToken));
// Update request with results // Update request with results using scoped service
request.Status = GeneticRequestStatus.Completed; request.Status = GeneticRequestStatus.Completed;
request.CompletedAt = DateTime.UtcNow; request.CompletedAt = DateTime.UtcNow;
request.BestFitness = results.BestFitness; request.BestFitness = results.BestFitness;
request.BestIndividual = results.BestIndividual; request.BestIndividual = results.BestIndividual;
request.ProgressInfo = results.ProgressInfo; request.ProgressInfo = results.ProgressInfo;
_geneticService.UpdateGeneticRequest(request); await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
_logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId); _logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId);
} }
@@ -88,7 +90,8 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
request.Status = GeneticRequestStatus.Failed; request.Status = GeneticRequestStatus.Failed;
request.ErrorMessage = ex.Message; request.ErrorMessage = ex.Message;
request.CompletedAt = DateTime.UtcNow; request.CompletedAt = DateTime.UtcNow;
_geneticService.UpdateGeneticRequest(request); await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
_logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId); _logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId);
} }
@@ -100,6 +103,4 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
throw; throw;
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -12,12 +11,12 @@ public class LeaderboardWorker : BaseWorker<LeaderboardWorker>
public LeaderboardWorker( public LeaderboardWorker(
ILogger<LeaderboardWorker> logger, ILogger<LeaderboardWorker> logger,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
_workerType, _workerType,
logger, logger,
TimeSpan.FromHours(24), TimeSpan.FromHours(24),
workerService serviceProvider
) )
{ {
_statisticService = statisticService; _statisticService = statisticService;

View File

@@ -14,7 +14,7 @@ public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
public PricesBaseWorker( public PricesBaseWorker(
ILogger<T> logger, ILogger<T> logger,
IPricesService pricesService, IPricesService pricesService,
IWorkerService workerService, IServiceProvider serviceProvider,
IStatisticService statisticService, IStatisticService statisticService,
TimeSpan delay, TimeSpan delay,
WorkerType workerType, WorkerType workerType,
@@ -22,7 +22,7 @@ public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
workerType, workerType,
logger, logger,
delay, delay,
workerService serviceProvider
) )
{ {
_pricesService = pricesService; _pricesService = pricesService;

View File

@@ -10,11 +10,11 @@ public class PricesFifteenMinutesWorker : PricesBaseWorker<PricesFifteenMinutesW
public PricesFifteenMinutesWorker( public PricesFifteenMinutesWorker(
ILogger<PricesFifteenMinutesWorker> logger, ILogger<PricesFifteenMinutesWorker> logger,
IPricesService pricesService, IPricesService pricesService,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
logger, logger,
pricesService, pricesService,
workerService, serviceProvider,
statisticService, statisticService,
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1),
WorkerType.PriceFifteenMinutes, WorkerType.PriceFifteenMinutes,

View File

@@ -10,11 +10,11 @@ public class PricesFiveMinutesWorker : PricesBaseWorker<PricesFiveMinutesWorker>
public PricesFiveMinutesWorker( public PricesFiveMinutesWorker(
ILogger<PricesFiveMinutesWorker> logger, ILogger<PricesFiveMinutesWorker> logger,
IPricesService pricesService, IPricesService pricesService,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
logger, logger,
pricesService, pricesService,
workerService, serviceProvider,
statisticService, statisticService,
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1),
WorkerType.PriceFiveMinutes, WorkerType.PriceFiveMinutes,

View File

@@ -10,11 +10,11 @@ public class PricesFourHoursWorker : PricesBaseWorker<PricesFourHoursWorker>
public PricesFourHoursWorker( public PricesFourHoursWorker(
ILogger<PricesFourHoursWorker> logger, ILogger<PricesFourHoursWorker> logger,
IPricesService pricesService, IPricesService pricesService,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
logger, logger,
pricesService, pricesService,
workerService, serviceProvider,
statisticService, statisticService,
TimeSpan.FromHours(2), TimeSpan.FromHours(2),
WorkerType.PriceFourHour, WorkerType.PriceFourHour,

View File

@@ -10,11 +10,11 @@ public class PricesOneDayWorker : PricesBaseWorker<PricesOneDayWorker>
public PricesOneDayWorker( public PricesOneDayWorker(
ILogger<PricesOneDayWorker> logger, ILogger<PricesOneDayWorker> logger,
IPricesService pricesService, IPricesService pricesService,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
logger, logger,
pricesService, pricesService,
workerService, serviceProvider,
statisticService, statisticService,
TimeSpan.FromHours(12), TimeSpan.FromHours(12),
WorkerType.PriceOneDay, WorkerType.PriceOneDay,

View File

@@ -10,11 +10,11 @@ public class PricesOneHourWorker : PricesBaseWorker<PricesOneHourWorker>
public PricesOneHourWorker( public PricesOneHourWorker(
ILogger<PricesOneHourWorker> logger, ILogger<PricesOneHourWorker> logger,
IPricesService pricesService, IPricesService pricesService,
IStatisticService statisticService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( IStatisticService statisticService) : base(
logger, logger,
pricesService, pricesService,
workerService, serviceProvider,
statisticService, statisticService,
TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30),
WorkerType.PriceOneHour, WorkerType.PriceOneHour,

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Managing.Domain.Accounts;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -30,8 +31,11 @@ public class PricesService : IPricesService
{ {
try try
{ {
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); var account = new Account()
{
Exchange = exchange,
};
if (account == null) if (account == null)
throw new Exception($"Enable to found account for exchange {exchange}"); throw new Exception($"Enable to found account for exchange {exchange}");

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Common; using Managing.Common;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -11,12 +10,12 @@ public class SpotlightWorker : BaseWorker<SpotlightWorker>
public SpotlightWorker( public SpotlightWorker(
ILogger<SpotlightWorker> logger, ILogger<SpotlightWorker> logger,
IWorkerService workerService, IServiceProvider serviceProvider,
IStatisticService statisticService) : base( IStatisticService statisticService) : base(
Enums.WorkerType.Spotlight, Enums.WorkerType.Spotlight,
logger, logger,
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5),
workerService) serviceProvider)
{ {
_statisticService = statisticService; _statisticService = statisticService;
} }

View File

@@ -6,7 +6,6 @@ using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -55,7 +54,7 @@ public class StatisticService : IStatisticService
public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top) public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top)
{ {
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange); var account = (await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == exchange);
var date = DateTime.UtcNow; var date = DateTime.UtcNow;
if (account == null) if (account == null)
@@ -73,7 +72,7 @@ public class StatisticService : IStatisticService
foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker))) foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker)))
{ {
var volume = _exchangeService.GetVolume(account, ticker); var volume = _exchangeService.GetVolume(account, ticker);
var price = _exchangeService.GetPrice(account, ticker, date); var price = await _exchangeService.GetPrice(account, ticker, date);
volumeTickers.Add(ticker, volume * price); volumeTickers.Add(ticker, volume * price);
} }
@@ -138,7 +137,7 @@ public class StatisticService : IStatisticService
if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 5m) if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 5m)
{ {
await _messengerService.SendFundingRateUpdate(oldRate, newRate); await _messengerService.SendFundingRateUpdate(oldRate, newRate);
_statisticRepository.UpdateFundingRate(oldRate, newRate); await _statisticRepository.UpdateFundingRateAsync(oldRate, newRate);
} }
} }
} }
@@ -151,16 +150,21 @@ public class StatisticService : IStatisticService
oldRate.Direction == newRate.Direction; oldRate.Direction == newRate.Direction;
} }
public Task<List<FundingRate>> GetFundingRates() public async Task<List<FundingRate>> GetFundingRates()
{ {
var previousFundingRate = _statisticRepository.GetFundingRates(); var previousFundingRate = await _statisticRepository.GetFundingRatesAsync();
return Task.FromResult(previousFundingRate); return previousFundingRate;
} }
public IList<TopVolumeTicker> GetLastTopVolumeTicker() public IList<TopVolumeTicker> GetLastTopVolumeTicker()
{
return GetLastTopVolumeTickerAsync().Result;
}
public async Task<IList<TopVolumeTicker>> GetLastTopVolumeTickerAsync()
{ {
var from = DateTime.UtcNow.AddDays(-1); var from = DateTime.UtcNow.AddDays(-1);
return _statisticRepository.GetTopVolumeTickers(from); return await _statisticRepository.GetTopVolumeTickersAsync(from);
} }
public async Task<IList<Ticker>> GetTickers() public async Task<IList<Ticker>> GetTickers()
@@ -179,13 +183,14 @@ public class StatisticService : IStatisticService
public async Task UpdateSpotlight() public async Task UpdateSpotlight()
{ {
var scenarios = _tradingService.GetScenarios(); var scenarios = await _tradingService.GetScenariosAsync();
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm); var account =
(await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm);
if (account == null) if (account == null)
throw new Exception($"Enable to found default account"); throw new Exception($"Enable to found default account");
var overview = GetLastSpotlight(DateTime.Now.AddMinutes(-20)); var overview = await GetLastSpotlightAsync(DateTime.UtcNow.AddMinutes(-20));
if (overview != null) if (overview != null)
{ {
@@ -205,7 +210,7 @@ public class StatisticService : IStatisticService
overview = new SpotlightOverview overview = new SpotlightOverview
{ {
Spotlights = new List<Spotlight>(), Spotlights = new List<Spotlight>(),
DateTime = DateTime.Now, DateTime = DateTime.UtcNow,
Identifier = Guid.NewGuid(), Identifier = Guid.NewGuid(),
ScenarioCount = scenarios.Count(), ScenarioCount = scenarios.Count(),
}; };
@@ -225,33 +230,43 @@ public class StatisticService : IStatisticService
Scenario = scenario Scenario = scenario
}; };
var options = new ParallelOptions() // Use SemaphoreSlim to limit concurrency to 2 operations at a time
{ using var semaphore = new SemaphoreSlim(2, 2);
MaxDegreeOfParallelism = 2
};
_ = Parallel.ForEach(tickers, options, async ticker => var tickerTasks = tickers.Select(async ticker =>
{ {
spotlight.TickerSignals.Add(new TickerSignal await semaphore.WaitAsync();
try
{ {
Ticker = ticker, var tickerSignal = new TickerSignal
FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes), {
FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes), Ticker = ticker,
OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour), FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour), FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay) OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour),
}); FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour),
OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay)
};
return tickerSignal;
}
finally
{
semaphore.Release();
}
}); });
var tickerSignals = await Task.WhenAll(tickerTasks);
spotlight.TickerSignals.AddRange(tickerSignals.Where(ts => ts != null));
overview.Spotlights.Add(spotlight); overview.Spotlights.Add(spotlight);
_statisticRepository.UpdateSpotlightOverview(overview); await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
} }
overview.DateTime = DateTime.Now; overview.DateTime = DateTime.UtcNow;
_statisticRepository.UpdateSpotlightOverview(overview); await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
} }
private async Task<List<Signal>> 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
{ {
@@ -284,8 +299,8 @@ public class StatisticService : IStatisticService
var backtest = await _backtester.RunTradingBotBacktest( var backtest = await _backtester.RunTradingBotBacktest(
config, config,
DateTime.Now.AddDays(-7), DateTime.UtcNow.AddDays(-7),
DateTime.Now, DateTime.UtcNow,
null, null,
false, false,
false); false);
@@ -300,9 +315,14 @@ public class StatisticService : IStatisticService
return null; return null;
} }
public SpotlightOverview GetLastSpotlight(DateTime dateTime) public async Task<SpotlightOverview> GetLastSpotlight(DateTime dateTime)
{ {
var overviews = _statisticRepository.GetSpotlightOverviews(dateTime); return await GetLastSpotlightAsync(dateTime);
}
public async Task<SpotlightOverview> GetLastSpotlightAsync(DateTime dateTime)
{
var overviews = await _statisticRepository.GetSpotlightOverviewsAsync(dateTime);
if (overviews.Any()) if (overviews.Any())
{ {
@@ -314,17 +334,27 @@ public class StatisticService : IStatisticService
public List<Trader> GetBestTraders() public List<Trader> GetBestTraders()
{ {
return _statisticRepository.GetBestTraders(); return GetBestTradersAsync().Result;
}
public async Task<List<Trader>> GetBestTradersAsync()
{
return await _statisticRepository.GetBestTradersAsync();
} }
public List<Trader> GetBadTraders() public List<Trader> GetBadTraders()
{ {
return _statisticRepository.GetBadTraders(); return GetBadTradersAsync().Result;
}
public async Task<List<Trader>> GetBadTradersAsync()
{
return await _statisticRepository.GetBadTradersAsync();
} }
public async Task<List<Trade>> GetLeadboardPositons() public async Task<List<Trade>> GetLeadboardPositons()
{ {
var customWatchAccount = _tradingService.GetTradersWatch(); var customWatchAccount = await _tradingService.GetTradersWatch();
var trades = new List<Trade>(); var trades = new List<Trade>();
foreach (var trader in customWatchAccount) foreach (var trader in customWatchAccount)
@@ -337,7 +367,7 @@ public class StatisticService : IStatisticService
public async Task UpdateLeaderboard() public async Task UpdateLeaderboard()
{ {
var previousBestTraders = _statisticRepository.GetBestTraders(); var previousBestTraders = await _statisticRepository.GetBestTradersAsync();
var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader(); var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader();
// Update / Insert best trader // Update / Insert best trader
@@ -345,7 +375,7 @@ public class StatisticService : IStatisticService
{ {
if (previousBestTraders.Exists((p) => p.Address == trader.Address)) if (previousBestTraders.Exists((p) => p.Address == trader.Address))
{ {
_statisticRepository.UpdateBestTrader(trader); await _statisticRepository.UpdateBestTraderAsync(trader);
} }
else else
{ {
@@ -367,7 +397,7 @@ public class StatisticService : IStatisticService
public async Task UpdateNoobiesboard() public async Task UpdateNoobiesboard()
{ {
var previousBadTraders = _statisticRepository.GetBadTraders(); var previousBadTraders = await _statisticRepository.GetBadTradersAsync();
var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader(); var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader();
// Update / Insert best trader // Update / Insert best trader
@@ -375,7 +405,7 @@ public class StatisticService : IStatisticService
{ {
if (previousBadTraders.Exists((p) => p.Address == trader.Address)) if (previousBadTraders.Exists((p) => p.Address == trader.Address))
{ {
_statisticRepository.UpdateBadTrader(trader); await _statisticRepository.UpdateBadTraderAsync(trader);
} }
else else
{ {

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -22,12 +21,12 @@ public class TraderWatcher : BaseWorker<TraderWatcher>
/// <param name="workerService">The worker service to manage worker lifecycle.</param> /// <param name="workerService">The worker service to manage worker lifecycle.</param>
public TraderWatcher( public TraderWatcher(
ILogger<TraderWatcher> logger, ILogger<TraderWatcher> logger,
ITradingService tradingService, IServiceProvider serviceProvider,
IWorkerService workerService) : base( ITradingService tradingService) : base(
_workerType, _workerType,
logger, logger,
TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(120),
workerService serviceProvider
) )
{ {
_tradingService = tradingService; _tradingService = tradingService;

View File

@@ -24,7 +24,7 @@ public class WorkerService : IWorkerService
var worker = new Worker() var worker = new Worker()
{ {
WorkerType = workerType, WorkerType = workerType,
StartTime = DateTime.Now, StartTime = DateTime.UtcNow,
LastRunTime = null, LastRunTime = null,
ExecutionCount = 0, ExecutionCount = 0,
Delay = delay Delay = delay
@@ -47,9 +47,9 @@ public class WorkerService : IWorkerService
await _workerRepository.EnableWorker(workerType); await _workerRepository.EnableWorker(workerType);
} }
public IEnumerable<Worker> GetWorkers() public async Task<IEnumerable<Worker>> GetWorkers()
{ {
return _workerRepository.GetWorkers(); return await _workerRepository.GetWorkers();
} }
public async Task<bool> ToggleWorker(Enums.WorkerType workerType) public async Task<bool> ToggleWorker(Enums.WorkerType workerType)
@@ -66,4 +66,4 @@ public class WorkerService : IWorkerService
return true; return true;
} }
} }
} }

View File

@@ -12,13 +12,13 @@ namespace Managing.Application.Abstractions
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns> /// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config); Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary> /// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class /// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance configured for backtesting</returns> /// <returns>ITradingBot instance configured for backtesting</returns>
ITradingBot CreateBacktestTradingBot(TradingBotConfig config); Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
} }
} }

View File

@@ -7,38 +7,38 @@ namespace Managing.Application.Abstractions;
public interface IBotService public interface IBotService
{ {
void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data); Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data);
void AddSimpleBotToCache(IBot bot); void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot); void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots(); List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots(); Task<IEnumerable<BotBackup>> GetSavedBotsAsync();
void StartBotFromBackup(BotBackup backupBot); Task StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string identifier); Task<BotBackup> GetBotBackup(string identifier);
/// <summary> /// <summary>
/// Creates a trading bot using the unified TradingBot class /// Creates a trading bot using the unified TradingBot class
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns> /// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config); Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary> /// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class /// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary> /// </summary>
/// <param name="config">The trading bot configuration</param> /// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance configured for backtesting</returns> /// <returns>ITradingBot instance configured for backtesting</returns>
ITradingBot CreateBacktestTradingBot(TradingBotConfig config); Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
// Legacy methods - these will use TradingBot internally but maintain backward compatibility // Legacy methods - these will use TradingBot internally but maintain backward compatibility
ITradingBot CreateScalpingBot(TradingBotConfig config); Task<ITradingBot> CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config); Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config); Task<ITradingBot> CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(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);
Task<string> RestartBot(string botName); Task<string> RestartBot(string botName);
void ToggleIsForWatchingOnly(string botName); Task ToggleIsForWatchingOnly(string botName);
Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig); Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig);
} }

View File

@@ -7,14 +7,10 @@ namespace Managing.Application.Abstractions
{ {
public interface IScenarioService public interface IScenarioService
{ {
IEnumerable<Scenario> GetScenarios(); Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1); Task<IEnumerable<Indicator>> GetIndicatorsAsync();
IEnumerable<Indicator> GetIndicators();
bool DeleteStrategy(string name);
bool DeleteScenario(string name);
Scenario GetScenario(string name);
Indicator CreateStrategy(IndicatorType type, Task<Indicator> CreateStrategy(IndicatorType type,
string name, string name,
int? period = null, int? period = null,
int? fastPeriods = null, int? fastPeriods = null,
@@ -25,21 +21,19 @@ namespace Managing.Application.Abstractions
int? smoothPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null); int? cyclePeriods = null);
bool DeleteStrategies(); Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
bool DeleteScenarios();
bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
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);
IEnumerable<Scenario> GetScenariosByUser(User user); Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1); Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
IEnumerable<Indicator> GetIndicatorsByUser(User user); Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user);
bool DeleteIndicatorByUser(User user, string name); Task<bool> DeleteIndicatorByUser(User user, string name);
bool DeleteScenarioByUser(User user, string name); Task<bool> DeleteScenarioByUser(User user, string name);
Scenario GetScenarioByUser(User user, string name); Task<Scenario> GetScenarioByUser(User user, string name);
Indicator CreateIndicatorForUser(User user, Task<Indicator> CreateIndicatorForUser(User user,
IndicatorType type, IndicatorType type,
string name, string name,
int? period = null, int? period = null,
@@ -51,11 +45,11 @@ namespace Managing.Application.Abstractions
int? smoothPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null); int? cyclePeriods = null);
bool DeleteStrategiesByUser(User user); Task<bool> DeleteStrategiesByUser(User user);
bool DeleteScenariosByUser(User user); Task<bool> DeleteScenariosByUser(User user);
bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod); Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
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);
} }

View File

@@ -1,8 +1,10 @@
namespace Managing.Application.Abstractions; using Managing.Domain.Users;
namespace Managing.Application.Abstractions;
public interface ISettingsService public interface ISettingsService
{ {
bool SetupSettings(); Task<bool> SetupSettings();
Task<bool> ResetSettings(); Task<bool> ResetSettings();
Task<bool> CreateDefaultConfiguration(Domain.Users.User user); Task<bool> CreateDefaultConfiguration(User user);
} }

View File

@@ -3,7 +3,6 @@ using Managing.Domain.Accounts;
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.Strategies;
using Managing.Domain.Strategies.Base; using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -16,7 +15,7 @@ namespace Managing.Application.Abstractions
Account Account { get; set; } Account Account { get; set; }
FixedSizeQueue<Candle> OptimizedCandles { get; set; } FixedSizeQueue<Candle> OptimizedCandles { get; set; }
HashSet<Candle> Candles { get; set; } HashSet<Candle> Candles { get; set; }
HashSet<Signal> Signals { get; set; } HashSet<LightSignal> Signals { get; set; }
List<Position> Positions { get; set; } List<Position> Positions { get; set; }
Dictionary<DateTime, decimal> WalletBalances { get; set; } Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; } Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
@@ -24,7 +23,7 @@ namespace Managing.Application.Abstractions
DateTime CreateDate { get; } DateTime CreateDate { get; }
DateTime PreloadSince { get; set; } DateTime PreloadSince { get; set; }
int PreloadedCandlesCount { get; set; } int PreloadedCandlesCount { get; set; }
decimal Fee { get; set; }
Task Run(); Task Run();
Task ToggleIsForWatchOnly(); Task ToggleIsForWatchOnly();
@@ -36,7 +35,7 @@ namespace Managing.Application.Abstractions
Task LoadAccount(); Task LoadAccount();
Task<Position> OpenPositionManually(TradeDirection direction); Task<Position> OpenPositionManually(TradeDirection direction);
Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
bool tradeClosingPosition = false); bool tradeClosingPosition = false);
/// <summary> /// <summary>

View File

@@ -100,9 +100,18 @@ public class AccountService : IAccountService
public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance) public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
{ {
var account = await _accountRepository.GetAccountByNameAsync(name); var account = await _accountRepository.GetAccountByNameAsync(name);
if (account == null)
{
throw new ArgumentException($"Account '{name}' not found");
}
ManageProperties(hideSecrets, getBalance, account); ManageProperties(hideSecrets, getBalance, account);
account.User = await _userRepository.GetUserByNameAsync(account.User.Name); if (account.User == null && account.User != null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
}
return account; return account;
} }
@@ -123,9 +132,31 @@ public class AccountService : IAccountService
return account; return account;
} }
public IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance) public async Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true,
bool getBalance = false)
{ {
var result = _accountRepository.GetAccounts(); var account = await _accountRepository.GetAccountByNameAsync(accountName);
if (account != null)
{
ManageProperties(hideSecrets, getBalance, account);
if (account.User != null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
}
}
return account;
}
public async Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
{
return await GetAccountsAsync(hideSecrets, getBalance);
}
public async Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance)
{
var result = await _accountRepository.GetAccountsAsync();
var accounts = new List<Account>(); var accounts = new List<Account>();
foreach (var account in result) foreach (var account in result)
@@ -139,15 +170,21 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true) public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
{ {
var cacheKey = $"user-account-{user.Name}"; return GetAccountsByUserAsync(user, hideSecrets).Result;
return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
TimeSpan.FromMinutes(5));
} }
private IEnumerable<Account> GetAccounts(User user, bool hideSecrets, bool getBalance) public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true)
{ {
var result = _accountRepository.GetAccounts(); var cacheKey = $"user-account-{user.Name}";
// For now, we'll get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching
return await GetAccountsAsync(user, hideSecrets, false);
}
private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
{
var result = await _accountRepository.GetAccountsAsync();
var accounts = new List<Account>(); var accounts = new List<Account>();
foreach (var account in result.Where(a => a.User.Name == user.Name)) foreach (var account in result.Where(a => a.User.Name == user.Name))
@@ -161,11 +198,16 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets) public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
{ {
var cacheKey = $"user-account-balance-{user.Name}"; return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); }, }
TimeSpan.FromHours(3));
return accounts; public async Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets)
{
var cacheKey = $"user-account-balance-{user.Name}";
// For now, get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching
return await GetAccountsAsync(user, hideSecrets, true);
} }
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName) public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName)
@@ -200,7 +242,8 @@ public class AccountService : IAccountService
} }
} }
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5) public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
{ {
// Get the account for the user // Get the account for the user
var account = await GetAccountByUser(user, accountName, true, false); var account = await GetAccountByUser(user, accountName, true, false);
@@ -220,12 +263,12 @@ public class AccountService : IAccountService
{ {
// Call the Web3ProxyService to swap GMX tokens // Call the Web3ProxyService to swap GMX tokens
var swapInfos = await _web3ProxyService.SwapGmxTokensAsync( var swapInfos = await _web3ProxyService.SwapGmxTokensAsync(
account.Key, account.Key,
fromTicker, fromTicker,
toTicker, toTicker,
amount, amount,
orderType, orderType,
triggerRatio, triggerRatio,
allowedSlippage allowedSlippage
); );
@@ -239,7 +282,8 @@ public class AccountService : IAccountService
} }
} }
public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null) public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker,
decimal amount, int? chainId = null)
{ {
// Get the account for the user // Get the account for the user
var account = await GetAccountByUser(user, accountName, true, false); var account = await GetAccountByUser(user, accountName, true, false);
@@ -271,10 +315,10 @@ public class AccountService : IAccountService
{ {
// Call the Web3ProxyService to send tokens // Call the Web3ProxyService to send tokens
var swapInfos = await _web3ProxyService.SendTokenAsync( var swapInfos = await _web3ProxyService.SendTokenAsync(
account.Key, account.Key,
recipientAddress, recipientAddress,
ticker, ticker,
amount, amount,
chainId chainId
); );

View File

@@ -121,6 +121,12 @@ namespace Managing.Application.Backtesting
result.StartDate = startDate; result.StartDate = startDate;
result.EndDate = endDate; 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) if (save && user != null)
{ {
_backtestRepository.InsertBacktestForUser(user, result); _backtestRepository.InsertBacktestForUser(user, result);
@@ -138,8 +144,9 @@ namespace Managing.Application.Backtesting
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user); var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
if (refundSuccess) if (refundSuccess)
{ {
_logger.LogInformation( _logger.LogError(
"Successfully refunded credits for user {UserName} after backtest failure", user.Name); "Successfully refunded credits for user {UserName} after backtest failure: {message}",
user.Name, ex.Message);
} }
else else
{ {
@@ -188,7 +195,7 @@ namespace Managing.Application.Backtesting
string requestId = null, string requestId = null,
object metadata = null) object metadata = null)
{ {
var tradingBot = _botFactory.CreateBacktestTradingBot(config); var tradingBot = await _botFactory.CreateBacktestTradingBot(config);
// Scenario and indicators should already be loaded in constructor by BotService // Scenario and indicators should already be loaded in constructor by BotService
// This is just a validation check to ensure everything loaded properly // This is just a validation check to ensure everything loaded properly
@@ -215,26 +222,7 @@ namespace Managing.Application.Backtesting
private async Task<Account> GetAccountFromConfig(TradingBotConfig config) private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
{ {
var accounts = _accountService.GetAccounts(false, false).ToArray(); return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
var account = accounts.FirstOrDefault(a =>
a.Name.Equals(config.AccountName, StringComparison.OrdinalIgnoreCase) &&
a.Exchange == TradingExchanges.GmxV2);
if (account == null && accounts.Any())
{
account = accounts.First();
}
if (account != null)
{
return account;
}
return new Account
{
Name = config.AccountName,
Exchange = TradingExchanges.GmxV2
};
} }
private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe, private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
@@ -270,13 +258,13 @@ namespace Managing.Application.Backtesting
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}", _logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
totalCandles, config.Ticker, config.Timeframe); totalCandles, config.Ticker, config.Timeframe);
bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance); bot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
foreach (var candle in candles) foreach (var candle in candles)
{ {
bot.OptimizedCandles.Enqueue(candle); bot.OptimizedCandles.Enqueue(candle);
bot.Candles.Add(candle); bot.Candles.Add(candle);
bot.Run(); await bot.Run();
currentCandle++; currentCandle++;
@@ -318,8 +306,6 @@ namespace Managing.Application.Backtesting
var finalPnl = bot.GetProfitAndLoss(); var finalPnl = bot.GetProfitAndLoss();
var winRate = bot.GetWinRate(); var winRate = bot.GetWinRate();
var optimizedMoneyManagement =
TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement);
var stats = TradingHelpers.GetStatistics(bot.WalletBalances); var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
var growthPercentage = var growthPercentage =
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl); TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
@@ -357,7 +343,6 @@ namespace Managing.Application.Backtesting
Fees = fees, Fees = fees,
WalletBalances = bot.WalletBalances.ToList(), WalletBalances = bot.WalletBalances.ToList(),
Statistics = stats, Statistics = stats,
OptimizedMoneyManagement = optimizedMoneyManagement,
IndicatorsValues = withCandles IndicatorsValues = withCandles
? AggregateValues(indicatorsValues, bot.IndicatorsValues) ? AggregateValues(indicatorsValues, bot.IndicatorsValues)
: new Dictionary<IndicatorType, IndicatorsResultBase>(), : new Dictionary<IndicatorType, IndicatorsResultBase>(),
@@ -442,11 +427,11 @@ namespace Managing.Application.Backtesting
return indicatorsValues; return indicatorsValues;
} }
public bool DeleteBacktest(string id) public async Task<bool> DeleteBacktestAsync(string id)
{ {
try try
{ {
_backtestRepository.DeleteBacktestByIdForUser(null, id); await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -476,12 +461,24 @@ namespace Managing.Application.Backtesting
return backtests; return backtests;
} }
public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
{
var backtests = await _backtestRepository.GetBacktestsByUserAsync(user);
return backtests;
}
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId) public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
{ {
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList(); var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
return backtests; return backtests;
} }
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
{
var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
return backtests;
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc") int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{ {
@@ -490,9 +487,19 @@ namespace Managing.Application.Backtesting
return (backtests, totalCount); return (backtests, totalCount);
} }
public Backtest GetBacktestByIdForUser(User user, string id) public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{ {
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id); var (backtests, totalCount) =
await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
sortOrder);
return (backtests, totalCount);
}
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
{
var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);
if (backtest == null) if (backtest == null)
return null; return null;
@@ -504,12 +511,12 @@ namespace Managing.Application.Backtesting
var account = new Account var account = new Account
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm }; { Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
var candles = _exchangeService.GetCandlesInflux( var candles = await _exchangeService.GetCandlesInflux(
account.Exchange, account.Exchange,
backtest.Config.Ticker, backtest.Config.Ticker,
backtest.StartDate, backtest.StartDate,
backtest.Config.Timeframe, backtest.Config.Timeframe,
backtest.EndDate).Result; backtest.EndDate);
if (candles != null && candles.Count > 0) if (candles != null && candles.Count > 0)
{ {
@@ -525,11 +532,11 @@ namespace Managing.Application.Backtesting
return backtest; return backtest;
} }
public bool DeleteBacktestByUser(User user, string id) public async Task<bool> DeleteBacktestByUserAsync(User user, string id)
{ {
try try
{ {
_backtestRepository.DeleteBacktestByIdForUser(user, id); await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -539,11 +546,11 @@ namespace Managing.Application.Backtesting
} }
} }
public bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids) public async Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids)
{ {
try try
{ {
_backtestRepository.DeleteBacktestsByIdsForUser(user, ids); await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -567,11 +574,11 @@ namespace Managing.Application.Backtesting
} }
} }
public bool DeleteBacktestsByRequestId(string requestId) public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
{ {
try try
{ {
_backtestRepository.DeleteBacktestsByRequestId(requestId); await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -589,6 +596,14 @@ namespace Managing.Application.Backtesting
return (backtests, totalCount); return (backtests, totalCount);
} }
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(
User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var (backtests, totalCount) =
await _backtestRepository.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
return (backtests, totalCount);
}
// Bundle backtest methods // Bundle backtest methods
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest) public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{ {
@@ -600,27 +615,53 @@ namespace Managing.Application.Backtesting
return _backtestRepository.GetBundleBacktestRequestsByUser(user); return _backtestRepository.GetBundleBacktestRequestsByUser(user);
} }
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
{
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id) public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
{ {
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id); return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
} }
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
{
return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
}
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest) public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{ {
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest); _backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
} }
public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
{
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
}
public void DeleteBundleBacktestRequestByIdForUser(User user, string id) public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
{ {
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id); _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
} }
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
{
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status) public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{ {
// Use the repository method to get all bundles, then filter by status // Use the repository method to get all bundles, then filter by status
return _backtestRepository.GetBundleBacktestRequestsByStatus(status); return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
} }
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(
BundleBacktestRequestStatus status)
{
return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status);
}
/// <summary> /// <summary>
/// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request. /// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
/// </summary> /// </summary>

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -14,6 +15,7 @@ namespace Managing.Application.Bots.Base
private readonly ILogger<TradingBot> _tradingBotLogger; private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService; private readonly ITradingService _tradingService;
private readonly IBotService _botService; private readonly IBotService _botService;
private readonly IBackupBotService _backupBotService;
public BotFactory( public BotFactory(
IExchangeService exchangeService, IExchangeService exchangeService,
@@ -21,7 +23,8 @@ namespace Managing.Application.Bots.Base
IMessengerService messengerService, IMessengerService messengerService,
IAccountService accountService, IAccountService accountService,
ITradingService tradingService, ITradingService tradingService,
IBotService botService) IBotService botService,
IBackupBotService backupBotService)
{ {
_tradingBotLogger = tradingBotLogger; _tradingBotLogger = tradingBotLogger;
_exchangeService = exchangeService; _exchangeService = exchangeService;
@@ -29,23 +32,24 @@ namespace Managing.Application.Bots.Base
_accountService = accountService; _accountService = accountService;
_tradingService = tradingService; _tradingService = tradingService;
_botService = botService; _botService = botService;
_backupBotService = backupBotService;
} }
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow) IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
{ {
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService); return new SimpleBot(botName, _tradingBotLogger, workflow, _botService, _backupBotService);
} }
ITradingBot IBotFactory.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
return _botService.CreateTradingBot(config); return await _botService.CreateTradingBot(config);
} }
ITradingBot IBotFactory.CreateBacktestTradingBot(TradingBotConfig config) public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
{ {
// Delegate to BotService which handles scenario loading properly // Delegate to BotService which handles scenario loading properly
return _botService.CreateBacktestTradingBot(config); return await _botService.CreateBacktestTradingBot(config);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.ManageBot;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -10,13 +11,16 @@ namespace Managing.Application.Bots
{ {
public readonly ILogger<TradingBot> Logger; public readonly ILogger<TradingBot> Logger;
private readonly IBotService _botService; private readonly IBotService _botService;
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<TradingBot> logger, Workflow workflow, IBotService botService,
IBackupBotService backupBotService) :
base(name) base(name)
{ {
Logger = logger; Logger = logger;
_botService = botService; _botService = botService;
_backupBotService = backupBotService;
_workflow = workflow; _workflow = workflow;
Interval = 100; Interval = 100;
} }
@@ -35,20 +39,20 @@ namespace Managing.Application.Bots
Logger.LogInformation(Identifier); Logger.LogInformation(Identifier);
Logger.LogInformation(DateTime.Now.ToString()); Logger.LogInformation(DateTime.Now.ToString());
await _workflow.Execute(); await _workflow.Execute();
SaveBackup(); await SaveBackup();
Logger.LogInformation("__________________________________________________"); Logger.LogInformation("__________________________________________________");
}); });
} }
public override void SaveBackup() public override async Task SaveBackup()
{ {
var data = JsonConvert.SerializeObject(_workflow); var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveOrUpdateBotBackup(User, Identifier, Status, data); await _backupBotService.SaveOrUpdateBotBackup(User, Identifier, Status, new TradingBotBackup());
} }
public override void LoadBackup(BotBackup backup) public override void LoadBackup(BotBackup backup)
{ {
_workflow = JsonConvert.DeserializeObject<Workflow>(backup.Data); _workflow = new Workflow();
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,14 @@ using System.Text.Json;
using GeneticSharp; using GeneticSharp;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk; using Managing.Domain.Risk;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Users; using Managing.Domain.Users;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -22,6 +24,7 @@ public class GeneticService : IGeneticService
private readonly IBacktester _backtester; private readonly IBacktester _backtester;
private readonly ILogger<GeneticService> _logger; private readonly ILogger<GeneticService> _logger;
private readonly IMessengerService _messengerService; private readonly IMessengerService _messengerService;
private readonly IServiceScopeFactory _serviceScopeFactory;
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx) // Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new() public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
@@ -188,12 +191,14 @@ public class GeneticService : IGeneticService
IGeneticRepository geneticRepository, IGeneticRepository geneticRepository,
IBacktester backtester, IBacktester backtester,
ILogger<GeneticService> logger, ILogger<GeneticService> logger,
IMessengerService messengerService) IMessengerService messengerService,
IServiceScopeFactory serviceScopeFactory)
{ {
_geneticRepository = geneticRepository; _geneticRepository = geneticRepository;
_backtester = backtester; _backtester = backtester;
_logger = logger; _logger = logger;
_messengerService = messengerService; _messengerService = messengerService;
_serviceScopeFactory = serviceScopeFactory;
} }
public GeneticRequest CreateGeneticRequest( public GeneticRequest CreateGeneticRequest(
@@ -247,9 +252,9 @@ public class GeneticService : IGeneticService
return _geneticRepository.GetGeneticRequestByIdForUser(user, id); return _geneticRepository.GetGeneticRequestByIdForUser(user, id);
} }
public void UpdateGeneticRequest(GeneticRequest geneticRequest) public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest)
{ {
_geneticRepository.UpdateGeneticRequest(geneticRequest); await _geneticRepository.UpdateGeneticRequestAsync(geneticRequest);
} }
public void DeleteGeneticRequestByIdForUser(User user, string id) public void DeleteGeneticRequestByIdForUser(User user, string id)
@@ -257,9 +262,9 @@ public class GeneticService : IGeneticService
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id); _geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
} }
public IEnumerable<GeneticRequest> GetPendingGeneticRequests() public Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status)
{ {
return _geneticRepository.GetPendingGeneticRequests(); return _geneticRepository.GetGeneticRequestsAsync(status);
} }
/// <summary> /// <summary>
@@ -277,7 +282,7 @@ public class GeneticService : IGeneticService
// Update status to running // Update status to running
request.Status = GeneticRequestStatus.Running; request.Status = GeneticRequestStatus.Running;
UpdateGeneticRequest(request); await UpdateGeneticRequestAsync(request);
// Create or resume chromosome for trading bot configuration // Create or resume chromosome for trading bot configuration
TradingBotChromosome chromosome; TradingBotChromosome chromosome;
@@ -307,7 +312,7 @@ public class GeneticService : IGeneticService
} }
// Create fitness function first // Create fitness function first
var fitness = new TradingBotFitness(_backtester, request, _logger); var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
// Create genetic algorithm with better configuration // Create genetic algorithm with better configuration
var ga = new GeneticAlgorithm( var ga = new GeneticAlgorithm(
@@ -341,7 +346,7 @@ public class GeneticService : IGeneticService
// Run the genetic algorithm with periodic checks for cancellation // Run the genetic algorithm with periodic checks for cancellation
var generationCount = 0; var generationCount = 0;
ga.GenerationRan += (sender, e) => ga.GenerationRan += async (sender, e) =>
{ {
generationCount = ga.GenerationsNumber; generationCount = ga.GenerationsNumber;
@@ -362,7 +367,7 @@ public class GeneticService : IGeneticService
request.BestChromosome = JsonSerializer.Serialize(geneValues); request.BestChromosome = JsonSerializer.Serialize(geneValues);
} }
UpdateGeneticRequest(request); await UpdateGeneticRequestAsync(request);
// Check for cancellation // Check for cancellation
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@@ -381,7 +386,7 @@ public class GeneticService : IGeneticService
// Update request status to pending so it can be resumed // Update request status to pending so it can be resumed
request.Status = GeneticRequestStatus.Pending; request.Status = GeneticRequestStatus.Pending;
UpdateGeneticRequest(request); await UpdateGeneticRequestAsync(request);
return new GeneticAlgorithmResult return new GeneticAlgorithmResult
{ {
@@ -413,7 +418,7 @@ public class GeneticService : IGeneticService
completed_at = DateTime.UtcNow completed_at = DateTime.UtcNow
}); });
UpdateGeneticRequest(request); await UpdateGeneticRequestAsync(request);
// Send notification about the completed genetic algorithm // Send notification about the completed genetic algorithm
try try
@@ -442,7 +447,7 @@ public class GeneticService : IGeneticService
request.Status = GeneticRequestStatus.Failed; request.Status = GeneticRequestStatus.Failed;
request.ErrorMessage = ex.Message; request.ErrorMessage = ex.Message;
request.CompletedAt = DateTime.UtcNow; request.CompletedAt = DateTime.UtcNow;
UpdateGeneticRequest(request); await UpdateGeneticRequestAsync(request);
throw; throw;
} }
@@ -505,7 +510,7 @@ public class TradingBotChromosome : ChromosomeBase
private readonly List<IndicatorType> _eligibleIndicators; private readonly List<IndicatorType> _eligibleIndicators;
private readonly double _maxTakeProfit; private readonly double _maxTakeProfit;
private readonly Random _random = new Random(); private readonly Random _random = new Random();
private int[]? _indicatorSelectionPattern; private int[] _indicatorSelectionPattern;
// Gene structure: // Gene structure:
// 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak) // 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
@@ -552,7 +557,7 @@ public class TradingBotChromosome : ChromosomeBase
GenerateIndicatorSelectionPattern(); GenerateIndicatorSelectionPattern();
} }
return new Gene(_indicatorSelectionPattern![geneIndex - 5]); return new Gene(_indicatorSelectionPattern[geneIndex - 5]);
} }
else else
{ {
@@ -790,7 +795,7 @@ public class TradingBotChromosome : ChromosomeBase
return _random.Next((int)range.min, (int)range.max + 1); return _random.Next((int)range.min, (int)range.max + 1);
} }
private string? GetParameterName(int index) private string GetParameterName(int index)
{ {
return index switch return index switch
{ {
@@ -879,14 +884,14 @@ public class GeneticIndicator
/// </summary> /// </summary>
public class TradingBotFitness : IFitness public class TradingBotFitness : IFitness
{ {
private readonly IBacktester _backtester; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly GeneticRequest _request; private readonly GeneticRequest _request;
private GeneticAlgorithm _geneticAlgorithm; private GeneticAlgorithm _geneticAlgorithm;
private readonly ILogger<GeneticService> _logger; private readonly ILogger<GeneticService> _logger;
public TradingBotFitness(IBacktester backtester, GeneticRequest request, ILogger<GeneticService> logger) public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request, ILogger<GeneticService> logger)
{ {
_backtester = backtester; _serviceScopeFactory = serviceScopeFactory;
_request = request; _request = request;
_logger = logger; _logger = logger;
} }
@@ -909,19 +914,22 @@ public class TradingBotFitness : IFitness
// Get current generation number (default to 0 if not available) // Get current generation number (default to 0 if not available)
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0; var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
// Run backtest // Run backtest using scoped service to avoid DbContext concurrency issues
var backtest = _backtester.RunTradingBotBacktest( var backtest = ServiceScopeHelpers.WithScopedService<IBacktester, Backtest>(
config, _serviceScopeFactory,
_request.StartDate, backtester => backtester.RunTradingBotBacktest(
_request.EndDate, config,
_request.User, _request.StartDate,
true, _request.EndDate,
false, // Don't include candles _request.User,
_request.RequestId, true,
new false, // Don't include candles
{ _request.RequestId,
generation = currentGeneration new
} {
generation = currentGeneration
}
)
).Result; ).Result;
// Calculate multi-objective fitness based on backtest results // Calculate multi-objective fitness based on backtest results
@@ -929,8 +937,9 @@ public class TradingBotFitness : IFitness
return fitness; return fitness;
} }
catch (Exception) catch (Exception ex)
{ {
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
// Return low fitness for failed backtests // Return low fitness for failed backtests
return 0.1; return 0.1;
} }

View File

@@ -0,0 +1,52 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
public interface IBackupBotService
{
Task<BotBackup> GetBotBackup(string identifier);
Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data);
}
public class BackupBotService : IBackupBotService
{
private readonly IBotRepository _botRepository;
public BackupBotService(IBotRepository botRepository)
{
_botRepository = botRepository;
}
public async Task<BotBackup> GetBotBackup(string identifier)
{
return await _botRepository.GetBotByIdentifierAsync(identifier);
}
public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
{
var backup = await GetBotBackup(identifier);
if (backup != null)
{
backup.LastStatus = status;
backup.Data = data;
await _botRepository.UpdateBackupBot(backup);
}
else
{
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
await _botRepository.InsertBotAsync(botBackup);
}
}
}
}

View File

@@ -6,8 +6,8 @@ using Managing.Application.Bots;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Users; using Managing.Domain.Users;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.ManageBot namespace Managing.Application.ManageBot
@@ -22,13 +22,16 @@ namespace Managing.Application.ManageBot
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 IServiceScopeFactory _scopeFactory;
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<TradingBot> tradingBotLogger,
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService) ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory)
{ {
_botRepository = botRepository; _botRepository = botRepository;
_exchangeService = exchangeService; _exchangeService = exchangeService;
@@ -38,35 +41,8 @@ namespace Managing.Application.ManageBot
_tradingService = tradingService; _tradingService = tradingService;
_moneyManagementService = moneyManagementService; _moneyManagementService = moneyManagementService;
_userService = userService; _userService = userService;
} _backupBotService = backupBotService;
_scopeFactory = scopeFactory;
public BotBackup GetBotBackup(string identifier)
{
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
}
public void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data)
{
var backup = GetBotBackup(identifier);
if (backup != null)
{
backup.LastStatus = status;
backup.Data = data;
_botRepository.UpdateBackupBot(backup);
}
else
{
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
_botRepository.InsertBotAsync(botBackup);
}
} }
public class BotTaskWrapper public class BotTaskWrapper
@@ -96,6 +72,27 @@ 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)
{
var user = await _userService.GetUser(backupBot.User.Name);
bot.User = user;
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
bot.LoadBackup(backupBot);
// Only start the bot if the backup status is Up
if (backupBot.LastStatus == BotStatus.Up)
{
// Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start());
}
else
{
// Keep the bot in Down status if it was originally Down
bot.Stop();
}
}
public List<ITradingBot> GetActiveBots() public List<ITradingBot> GetActiveBots()
{ {
var bots = _botTasks.Values var bots = _botTasks.Values
@@ -107,17 +104,17 @@ namespace Managing.Application.ManageBot
return bots; return bots;
} }
public IEnumerable<BotBackup> GetSavedBots() public async Task<IEnumerable<BotBackup>> GetSavedBotsAsync()
{ {
return _botRepository.GetBots(); return await _botRepository.GetBotsAsync();
} }
public void StartBotFromBackup(BotBackup backupBot) public async Task StartBotFromBackup(BotBackup backupBot)
{ {
object bot = null; object bot = null;
Task botTask = null; Task botTask = null;
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data); var scalpingBotData = backupBot.Data;
// Get the config directly from the backup // Get the config directly from the backup
var scalpingConfig = scalpingBotData.Config; var scalpingConfig = scalpingBotData.Config;
@@ -137,7 +134,7 @@ namespace Managing.Application.ManageBot
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName)) if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
scalpingConfig.Scenario = scenario; scalpingConfig.Scenario = scenario;
@@ -158,7 +155,7 @@ 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;
bot = CreateTradingBot(scalpingConfig); bot = await CreateTradingBot(scalpingConfig);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
if (bot != null && botTask != null) if (bot != null && botTask != null)
@@ -168,29 +165,38 @@ namespace Managing.Application.ManageBot
} }
} }
private void InitBot(ITradingBot bot, BotBackup backupBot) public async Task<BotBackup> GetBotBackup(string identifier)
{ {
var user = _userService.GetUser(backupBot.User.Name); return await _botRepository.GetBotByIdentifierAsync(identifier);
bot.User = user; }
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
bot.LoadBackup(backupBot);
// Only start the bot if the backup status is Up public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
if (backupBot.LastStatus == BotStatus.Up) {
var backup = await GetBotBackup(identifier);
if (backup != null)
{ {
// Start the bot asynchronously without waiting for completion backup.LastStatus = status;
_ = Task.Run(() => bot.Start()); backup.Data = data;
await _botRepository.UpdateBackupBot(backup);
} }
else else
{ {
// Keep the bot in Down status if it was originally Down var botBackup = new BotBackup
bot.Stop(); {
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
await _botRepository.InsertBotAsync(botBackup);
} }
} }
public IBot CreateSimpleBot(string botName, Workflow workflow) public IBot CreateSimpleBot(string botName, Workflow workflow)
{ {
return new SimpleBot(botName, _tradingBotLogger, workflow, this); return new SimpleBot(botName, _tradingBotLogger, workflow, this, _backupBotService);
} }
public async Task<string> StopBot(string identifier) public async Task<string> StopBot(string identifier)
@@ -263,7 +269,7 @@ namespace Managing.Application.ManageBot
// Restart the bot (this will update StartupTime) // Restart the bot (this will update StartupTime)
bot.Restart(); bot.Restart();
// Start the bot asynchronously without waiting for completion // Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start()); _ = Task.Run(() => bot.Start());
@@ -282,12 +288,12 @@ namespace Managing.Application.ManageBot
return BotStatus.Down.ToString(); return BotStatus.Down.ToString();
} }
public void ToggleIsForWatchingOnly(string identifier) public async Task ToggleIsForWatchingOnly(string identifier)
{ {
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) && if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
botTaskWrapper.BotInstance is ITradingBot tradingBot) botTaskWrapper.BotInstance is ITradingBot tradingBot)
{ {
tradingBot.ToggleIsForWatchOnly().Wait(); await tradingBot.ToggleIsForWatchOnly();
} }
} }
@@ -305,7 +311,7 @@ namespace Managing.Application.ManageBot
// 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))
{ {
var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
newConfig.Scenario = scenario; newConfig.Scenario = scenario;
@@ -365,12 +371,12 @@ namespace Managing.Application.ManageBot
} }
public 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
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -386,22 +392,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( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
public ITradingBot CreateBacktestTradingBot(TradingBotConfig config) public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
{ {
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -418,22 +417,15 @@ namespace Managing.Application.ManageBot
} }
config.IsForBacktest = true; config.IsForBacktest = true;
return new TradingBot( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
public ITradingBot CreateScalpingBot(TradingBotConfig config) public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
{ {
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -450,22 +442,15 @@ namespace Managing.Application.ManageBot
} }
config.FlipPosition = false; config.FlipPosition = false;
return new TradingBot( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config) public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
{ {
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -483,22 +468,15 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true; config.IsForBacktest = true;
config.FlipPosition = false; config.FlipPosition = false;
return new TradingBot( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
public ITradingBot CreateFlippingBot(TradingBotConfig config) public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
{ {
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -515,22 +493,15 @@ namespace Managing.Application.ManageBot
} }
config.FlipPosition = true; config.FlipPosition = true;
return new TradingBot( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config) public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
{ {
// Ensure the scenario is properly loaded from database if needed // Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName)) if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{ {
var scenario = _tradingService.GetScenarioByName(config.ScenarioName); var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null) if (scenario != null)
{ {
config.Scenario = scenario; config.Scenario = scenario;
@@ -548,14 +519,7 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true; config.IsForBacktest = true;
config.FlipPosition = true; config.FlipPosition = true;
return new TradingBot( return new TradingBot(_tradingBotLogger, _scopeFactory, config);
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
} }
} }
} }

View File

@@ -21,7 +21,7 @@ namespace Managing.Application.ManageBot
_accountService = accountService; _accountService = accountService;
} }
public async Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request, public Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var result = new Dictionary<User, List<ITradingBot>>(); var result = new Dictionary<User, List<ITradingBot>>();
@@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot
result[bot.User].Add(bot); result[bot.User].Add(bot);
} }
return result; return Task.FromResult(result);
} }
/// <summary> /// <summary>

View File

@@ -18,9 +18,9 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_botService = botService; _botService = botService;
} }
public Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken) public async Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
{ {
var backupBots = _botService.GetSavedBots().ToList(); var backupBots = (await _botService.GetSavedBotsAsync()).ToList();
_logger.LogInformation("Loading {Count} backup bots.", backupBots.Count); _logger.LogInformation("Loading {Count} backup bots.", backupBots.Count);
var result = new Dictionary<string, BotStatus>(); var result = new Dictionary<string, BotStatus>();
@@ -42,7 +42,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_botService.StartBotFromBackup(backupBot); _botService.StartBotFromBackup(backupBot);
// Wait a short time to allow the bot to initialize // Wait a short time to allow the bot to initialize
Thread.Sleep(1000); await Task.Delay(1000, cancellationToken);
// 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;
@@ -74,7 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
attempts++; attempts++;
if (attempts < maxAttempts) if (attempts < maxAttempts)
{ {
Thread.Sleep(1000); // Wait another second before next attempt await Task.Delay(1000, cancellationToken); // Wait another second before next attempt
} }
} }
@@ -114,7 +114,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus); _logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
return Task.FromResult(finalStatus.ToString()); return finalStatus.ToString();
} }
} }

View File

@@ -81,11 +81,11 @@ namespace Managing.Application.ManageBot
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
}; };
var tradingBot = _botFactory.CreateTradingBot(configToUse); var tradingBot = await _botFactory.CreateTradingBot(configToUse);
tradingBot.User = request.User; tradingBot.User = request.User;
// Log the configuration being used // Log the configuration being used
await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created"); LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
_botService.AddTradingBotToCache(tradingBot); _botService.AddTradingBotToCache(tradingBot);
return tradingBot.GetStatus(); return tradingBot.GetStatus();
@@ -98,7 +98,7 @@ namespace Managing.Application.ManageBot
/// </summary> /// </summary>
/// <param name="bot">The trading bot instance</param> /// <param name="bot">The trading bot instance</param>
/// <param name="context">Context information for the log</param> /// <param name="context">Context information for the log</param>
private async Task LogBotConfigurationAsync(ITradingBot bot, string context) private void LogBotConfigurationAsync(ITradingBot bot, string context)
{ {
try try
{ {

View File

@@ -1,35 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
</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.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
<PackageReference Include="Polly" Version="8.4.0" /> <PackageReference Include="Polly" Version="8.4.0"/>
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.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>

View File

@@ -38,9 +38,28 @@ public class MoneyManagementService : IMoneyManagementService
if (moneyManagement == null) if (moneyManagement == null)
{ {
request.User = user; // Convert MoneyManagement to LightMoneyManagement for insertion
await _settingsRepository.InsertMoneyManagement(request); var lightRequest = new LightMoneyManagement
return request; {
Name = request.Name,
Timeframe = request.Timeframe,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage
};
await _settingsRepository.InsertMoneyManagement(lightRequest, user);
// Return the created money management with user
return new MoneyManagement
{
Name = lightRequest.Name,
Timeframe = lightRequest.Timeframe,
StopLoss = lightRequest.StopLoss,
TakeProfit = lightRequest.TakeProfit,
Leverage = lightRequest.Leverage,
User = user
};
} }
else else
{ {
@@ -51,14 +70,28 @@ public class MoneyManagementService : IMoneyManagementService
"You do not have permission to update this money management strategy."); "You do not have permission to update this money management strategy.");
} }
moneyManagement.StopLoss = request.StopLoss; // Convert to LightMoneyManagement for update
moneyManagement.TakeProfit = request.TakeProfit; var lightRequest = new LightMoneyManagement
moneyManagement.Leverage = request.Leverage; {
moneyManagement.Timeframe = request.Timeframe; Name = request.Name,
moneyManagement.User = user; Timeframe = request.Timeframe,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage
};
_settingsRepository.UpdateMoneyManagement(moneyManagement); await _settingsRepository.UpdateMoneyManagementAsync(lightRequest, user);
return moneyManagement;
// Return updated money management
return new MoneyManagement
{
Name = lightRequest.Name,
Timeframe = lightRequest.Timeframe,
StopLoss = lightRequest.StopLoss,
TakeProfit = lightRequest.TakeProfit,
Leverage = lightRequest.Leverage,
User = user
};
} }
} }
@@ -67,18 +100,18 @@ public class MoneyManagementService : IMoneyManagementService
return await _settingsRepository.GetMoneyManagement(name); return await _settingsRepository.GetMoneyManagement(name);
} }
public IEnumerable<MoneyManagement> GetMoneyMangements(User user) public async Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user)
{ {
try try
{ {
// Try to use user-specific repository method first // Try to use user-specific repository method first
return _settingsRepository.GetMoneyManagementsByUser(user); return await _settingsRepository.GetMoneyManagementsByUserAsync(user);
} }
catch catch
{ {
// Fall back to filtering if user-specific endpoint is not implemented // Fall back to filtering if user-specific endpoint is not implemented
return _settingsRepository.GetMoneyManagements() var allMoneyManagements = await _settingsRepository.GetMoneyManagementsAsync();
.Where(mm => mm.User?.Name == user.Name); return allMoneyManagements.Where(mm => mm.User?.Name == user.Name);
} }
} }
@@ -106,19 +139,19 @@ public class MoneyManagementService : IMoneyManagementService
return moneyManagement; return moneyManagement;
} }
public bool DeleteMoneyManagement(User user, string name) public async Task<bool> DeleteMoneyManagement(User user, string name)
{ {
try try
{ {
try try
{ {
// Try to use user-specific repository method first // Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementByUser(user, name); await _settingsRepository.DeleteMoneyManagementByUserAsync(user, name);
} }
catch catch
{ {
// Fall back to verifying user ownership before deletion // Fall back to verifying user ownership before deletion
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result; var moneyManagement = await _settingsRepository.GetMoneyManagement(name);
if (moneyManagement != null && moneyManagement.User?.Name != user.Name) if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{ {
@@ -126,7 +159,7 @@ public class MoneyManagementService : IMoneyManagementService
"You do not have permission to delete this money management strategy."); "You do not have permission to delete this money management strategy.");
} }
_settingsRepository.DeleteMoneyManagement(name); await _settingsRepository.DeleteMoneyManagementAsync(name);
} }
return true; return true;
@@ -138,14 +171,14 @@ public class MoneyManagementService : IMoneyManagementService
} }
} }
public bool DeleteMoneyManagements(User user) public async Task<bool> DeleteMoneyManagements(User user)
{ {
try try
{ {
try try
{ {
// Try to use user-specific repository method first // Try to use user-specific repository method first
_settingsRepository.DeleteMoneyManagementsByUser(user); await _settingsRepository.DeleteMoneyManagementsByUserAsync(user);
} }
catch catch
{ {

View File

@@ -4,7 +4,6 @@ using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Users; using Managing.Domain.Users;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Scenarios namespace Managing.Application.Scenarios
@@ -20,20 +19,20 @@ namespace Managing.Application.Scenarios
_tradingService = tradingService; _tradingService = tradingService;
} }
public Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1) public async Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1)
{ {
var scenario = new Scenario(name, loopbackPeriod); var scenario = new Scenario(name, loopbackPeriod);
foreach (var strategy in strategies) foreach (var strategy in strategies)
{ {
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy)); scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
} }
try try
{ {
_tradingService.InsertScenario(scenario); await _tradingService.InsertScenarioAsync(scenario);
} }
catch (MongoCommandException ex) catch (Exception ex)
{ {
_logger.LogError(ex.Message); _logger.LogError(ex.Message);
throw new Exception("Cannot create scenario"); throw new Exception("Cannot create scenario");
@@ -42,7 +41,7 @@ namespace Managing.Application.Scenarios
return scenario; return scenario;
} }
public Indicator CreateStrategy( public async Task<Indicator> CreateStrategy(
IndicatorType type, IndicatorType type,
string name, string name,
int? period = null, int? period = null,
@@ -65,30 +64,25 @@ namespace Managing.Application.Scenarios
stochPeriods, stochPeriods,
smoothPeriods, smoothPeriods,
cyclePeriods); cyclePeriods);
_tradingService.InsertStrategy(strategy); await _tradingService.InsertStrategyAsync(strategy);
return strategy; return strategy;
} }
public IEnumerable<Scenario> GetScenarios() public async Task<IEnumerable<Scenario>> GetScenariosAsync()
{ {
return _tradingService.GetScenarios(); return await _tradingService.GetScenariosAsync();
} }
public Scenario GetScenario(string name) public async Task<IEnumerable<Indicator>> GetIndicatorsAsync()
{ {
return _tradingService.GetScenarioByName(name); return await _tradingService.GetStrategiesAsync();
} }
public IEnumerable<Indicator> GetIndicators() public async Task<bool> DeleteScenarioAsync(string name)
{
return _tradingService.GetStrategies();
}
public bool DeleteScenario(string name)
{ {
try try
{ {
_tradingService.DeleteScenario(name); await _tradingService.DeleteScenarioAsync(name);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -98,61 +92,19 @@ namespace Managing.Application.Scenarios
} }
} }
public bool DeleteStrategy(string name) public async Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
{ {
try try
{ {
_tradingService.DeleteStrategy(name); var scenario = await _tradingService.GetScenarioByNameAsync(name);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteStrategies()
{
try
{
_tradingService.DeleteStrategies();
}
catch (Exception)
{
return false;
}
return true;
}
public bool DeleteScenarios()
{
try
{
_tradingService.DeleteScenarios();
}
catch (Exception)
{
return false;
}
return true;
}
public bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
{
try
{
var scenario = _tradingService.GetScenarioByName(name);
scenario.Indicators.Clear(); scenario.Indicators.Clear();
foreach (var strategy in strategies) foreach (var strategy in strategies)
{ {
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy)); scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
} }
scenario.LoopbackPeriod = loopbackPeriod ?? 1; scenario.LoopbackPeriod = loopbackPeriod ?? 1;
_tradingService.UpdateScenario(scenario); await _tradingService.UpdateScenarioAsync(scenario);
return true; return true;
} }
catch (Exception e) catch (Exception e)
@@ -162,13 +114,13 @@ namespace Managing.Application.Scenarios
} }
} }
public bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, public async Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
int? slowPeriods, int? slowPeriods,
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods) int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{ {
try try
{ {
var strategy = _tradingService.GetStrategyByName(name); var strategy = await _tradingService.GetStrategyByNameAsync(name);
strategy.Type = indicatorType; strategy.Type = indicatorType;
strategy.Period = period; strategy.Period = period;
strategy.FastPeriods = fastPeriods; strategy.FastPeriods = fastPeriods;
@@ -178,7 +130,7 @@ namespace Managing.Application.Scenarios
strategy.StochPeriods = stochPeriods; strategy.StochPeriods = stochPeriods;
strategy.SmoothPeriods = smoothPeriods; strategy.SmoothPeriods = smoothPeriods;
strategy.CyclePeriods = cyclePeriods; strategy.CyclePeriods = cyclePeriods;
_tradingService.UpdateStrategy(strategy); await _tradingService.UpdateStrategyAsync(strategy);
return true; return true;
} }
catch (Exception e) catch (Exception e)
@@ -188,15 +140,13 @@ namespace Managing.Application.Scenarios
} }
} }
// User-specific methods implementation public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
public IEnumerable<Scenario> GetScenariosByUser(User user)
{ {
var scenarios = _tradingService.GetScenarios(); var scenarios = await _tradingService.GetScenariosAsync();
return scenarios.Where(s => s.User?.Name == user.Name); return scenarios.Where(s => s.User?.Name == user.Name);
} }
public 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)
{ {
@@ -205,82 +155,104 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies) foreach (var strategyName in strategies)
{ {
var strategy = _tradingService.GetStrategyByName(strategyName); var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name) if (strategy != null && strategy.User?.Name == user.Name)
{ {
scenario.AddIndicator(strategy); scenario.AddIndicator(strategy);
} }
} }
_tradingService.InsertScenario(scenario); await _tradingService.InsertScenarioAsync(scenario);
return scenario; return scenario;
} }
public IEnumerable<Indicator> GetIndicatorsByUser(User user) public async Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user)
{ {
var strategies = _tradingService.GetStrategies(); var indicators = await GetIndicatorsAsync();
return strategies.Where(s => s.User?.Name == user.Name); return indicators.Where(s => s.User?.Name == user.Name);
} }
public bool DeleteIndicatorByUser(User user, string name) public async Task<bool> DeleteIndicatorByUser(User user, string name)
{ {
var strategy = _tradingService.GetStrategyByName(name); var strategy = await _tradingService.GetStrategyByNameAsync(name);
if (strategy != null && strategy.User?.Name == user.Name) if (strategy != null && strategy.User?.Name == user.Name)
{ {
_tradingService.DeleteStrategy(strategy.Name); await _tradingService.DeleteStrategyAsync(strategy.Name);
return true; return true;
} }
return false; return false;
} }
public bool DeleteScenarioByUser(User user, string name) public async Task<bool> DeleteScenarioByUser(User user, string name)
{ {
var scenario = _tradingService.GetScenarioByName(name); try
if (scenario != null && scenario.User?.Name == user.Name)
{ {
_tradingService.DeleteScenario(scenario.Name); var scenarios = await GetScenariosByUserAsync(user);
foreach (var scenario in scenarios.Where(s => s.Name == name))
{
await _tradingService.DeleteScenarioAsync(scenario.Name);
}
return true; return true;
} }
catch (Exception ex)
return false; {
_logger.LogError(ex.Message);
return false;
}
} }
public Scenario GetScenarioByUser(User user, string name) public async Task<bool> DeleteScenariosByUser(User user)
{ {
var scenario = _tradingService.GetScenarioByName(name); try
{
var scenarios = await GetScenariosByUserAsync(user);
foreach (var scenario in scenarios)
{
await _tradingService.DeleteScenarioAsync(scenario.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public async Task<Scenario> GetScenarioByUser(User user, string name)
{
var scenario = await _tradingService.GetScenarioByNameAsync(name);
return scenario != null && scenario.User?.Name == user.Name ? scenario : null; return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
} }
public 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)
{ {
// Create a new strategy using the existing implementation // Create a new strategy using the existing implementation
var strategy = CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods, var strategy = await CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
multiplier, stochPeriods, smoothPeriods, cyclePeriods); multiplier, stochPeriods, smoothPeriods, cyclePeriods);
// Set the user // Set the user
strategy.User = user; strategy.User = user;
// Update the strategy to save the user property // Update the strategy to save the user property
_tradingService.UpdateStrategy(strategy); await _tradingService.UpdateStrategyAsync(strategy);
return strategy; return strategy;
} }
public bool DeleteStrategiesByUser(User user) public async Task<bool> DeleteStrategiesByUser(User user)
{ {
try try
{ {
var strategies = GetIndicatorsByUser(user); var strategies = await GetIndicatorsByUserAsync(user);
foreach (var strategy in strategies) foreach (var strategy in strategies)
{ {
_tradingService.DeleteStrategy(strategy.Name); await _tradingService.DeleteStrategyAsync(strategy.Name);
} }
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -290,29 +262,9 @@ namespace Managing.Application.Scenarios
} }
} }
public bool DeleteScenariosByUser(User user) public async Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
{ {
try var scenario = await _tradingService.GetScenarioByNameAsync(name);
{
var scenarios = GetScenariosByUser(user);
foreach (var scenario in scenarios)
{
_tradingService.DeleteScenario(scenario.Name);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
{
var scenario = _tradingService.GetScenarioByName(name);
if (scenario == null || scenario.User?.Name != user.Name) if (scenario == null || scenario.User?.Name != user.Name)
{ {
return false; return false;
@@ -323,29 +275,29 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies) foreach (var strategyName in strategies)
{ {
var strategy = _tradingService.GetStrategyByName(strategyName); var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name) if (strategy != null && strategy.User?.Name == user.Name)
{ {
scenario.AddIndicator(strategy); scenario.AddIndicator(strategy);
} }
} }
_tradingService.UpdateScenario(scenario); await _tradingService.UpdateScenarioAsync(scenario);
return true; return true;
} }
public bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, public async Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier, int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
int? stochPeriods, int? smoothPeriods, int? cyclePeriods) int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{ {
var strategy = _tradingService.GetStrategyByName(name); var strategy = await _tradingService.GetStrategyByNameAsync(name);
if (strategy == null || strategy.User?.Name != user.Name) if (strategy == null || strategy.User?.Name != user.Name)
{ {
return false; return false;
} }
// Use the existing update strategy logic // Use the existing update strategy logic
var result = UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods, var result = await UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods,
signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods); signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods);
return result; return result;

View File

@@ -127,7 +127,6 @@ public class MessengerService : IMessengerService
// If user is provided, also send to webhook // If user is provided, also send to webhook
if (user != null) if (user != null)
{ {
user = _userService.GetUser(user.Name);
await _webhookService.SendTradeNotification(user, message, isBadBehavior); await _webhookService.SendTradeNotification(user, message, isBadBehavior);
} }
} }

View File

@@ -32,18 +32,7 @@ public class SettingsService : ISettingsService
throw new Exception("Cannot delete all backtests"); throw new Exception("Cannot delete all backtests");
} }
if (!_scenarioService.DeleteScenarios()) if (!await SetupSettings())
{
throw new Exception("Cannot delete scenarios");
}
if (!_scenarioService.DeleteStrategies())
{
throw new Exception("Cannot delete all strategies");
}
if (!SetupSettings())
{ {
throw new Exception("Cannot setup settings"); throw new Exception("Cannot setup settings");
} }
@@ -51,7 +40,7 @@ public class SettingsService : ISettingsService
return await Task.FromResult(true); return await Task.FromResult(true);
} }
public bool SetupSettings() public async Task<bool> SetupSettings()
{ {
try try
{ {
@@ -59,7 +48,7 @@ public class SettingsService : ISettingsService
// SetupMoneyManagementsSeed(Timeframe.FifteenMinutes); // SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
// SetupMoneyManagementsSeed(Timeframe.OneHour); // SetupMoneyManagementsSeed(Timeframe.OneHour);
// SetupMoneyManagementsSeed(Timeframe.OneDay); // SetupMoneyManagementsSeed(Timeframe.OneDay);
SetupScenariosSeed(); await SetupScenariosSeed();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -85,107 +74,107 @@ public class SettingsService : ISettingsService
// await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement); // await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
// } // }
private void SetupScenariosSeed() private async Task SetupScenariosSeed()
{ {
SetupMacd(); await SetupMacd();
SetupRsiDiv(); await SetupRsiDiv();
SetupRsiDivConfirm(); await SetupRsiDivConfirm();
SetupSuperTrend(); await SetupSuperTrend();
SetupChandelierExit(); await SetupChandelierExit();
SetupStochRsiTrend(); await SetupStochRsiTrend();
SetupStochSTCTrend(); await SetupStochSTCTrend();
SetupEmaTrend(); await SetupEmaTrend();
SetupEmaCross(); await SetupEmaCross();
} }
private void SetupStochSTCTrend() private async Task SetupStochSTCTrend()
{ {
var name = "STCTrend"; var name = "STCTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.Stc, var strategy = await _scenarioService.CreateStrategy(IndicatorType.Stc,
name, name,
fastPeriods: 23, fastPeriods: 23,
slowPeriods: 50, slowPeriods: 50,
cyclePeriods: 10); cyclePeriods: 10);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupMacd() private async Task SetupMacd()
{ {
var name = "MacdCross"; var name = "MacdCross";
var strategy = _scenarioService.CreateStrategy(IndicatorType.MacdCross, var strategy = await _scenarioService.CreateStrategy(IndicatorType.MacdCross,
name, name,
fastPeriods: 12, fastPeriods: 12,
slowPeriods: 26, slowPeriods: 26,
signalPeriods: 9); signalPeriods: 9);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupRsiDiv() private async Task SetupRsiDiv()
{ {
var name = "RsiDiv6"; var name = "RsiDiv6";
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergence, var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergence,
name, name,
period: 6); period: 6);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupRsiDivConfirm() private async Task SetupRsiDivConfirm()
{ {
var name = "RsiDivConfirm6"; var name = "RsiDivConfirm6";
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm, var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm,
name, name,
period: 6); period: 6);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupSuperTrend() private async Task SetupSuperTrend()
{ {
var name = "SuperTrend"; var name = "SuperTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.SuperTrend, var strategy = await _scenarioService.CreateStrategy(IndicatorType.SuperTrend,
name, name,
period: 10, period: 10,
multiplier: 3); multiplier: 3);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupChandelierExit() private async Task SetupChandelierExit()
{ {
var name = "ChandelierExit"; var name = "ChandelierExit";
var strategy = _scenarioService.CreateStrategy(IndicatorType.ChandelierExit, var strategy = await _scenarioService.CreateStrategy(IndicatorType.ChandelierExit,
name, name,
period: 22, period: 22,
multiplier: 3); multiplier: 3);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupStochRsiTrend() private async Task SetupStochRsiTrend()
{ {
var name = "StochRsiTrend"; var name = "StochRsiTrend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend, var strategy = await _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend,
name, name,
period: 14, period: 14,
stochPeriods: 14, stochPeriods: 14,
signalPeriods: 3, signalPeriods: 3,
smoothPeriods: 1); smoothPeriods: 1);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupEmaTrend() private async Task SetupEmaTrend()
{ {
var name = "Ema200Trend"; var name = "Ema200Trend";
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaTrend, var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaTrend,
name, name,
period: 200); period: 200);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
private void SetupEmaCross() private async Task SetupEmaCross()
{ {
var name = "Ema200Cross"; var name = "Ema200Cross";
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaCross, var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaCross,
name, name,
period: 200); period: 200);
_scenarioService.CreateScenario(name, new List<string> { strategy.Name }); await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
} }
public async Task<bool> CreateDefaultConfiguration(User user) public async Task<bool> CreateDefaultConfiguration(User user)
@@ -212,7 +201,7 @@ public class SettingsService : ISettingsService
await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement); await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement);
// Create default Strategy (StcTrend) // Create default Strategy (StcTrend)
var defaultStrategy = _scenarioService.CreateIndicatorForUser( var defaultStrategy = await _scenarioService.CreateIndicatorForUser(
user, user,
IndicatorType.Stc, IndicatorType.Stc,
"Stc", "Stc",
@@ -226,7 +215,7 @@ public class SettingsService : ISettingsService
// Create default Scenario containing the strategy // Create default Scenario containing the strategy
var strategyNames = new List<string> { defaultStrategy.Name }; var strategyNames = new List<string> { defaultStrategy.Name };
var defaultScenario = _scenarioService.CreateScenarioForUser( var defaultScenario = await _scenarioService.CreateScenarioForUser(
user, user,
"STC Scenario", "STC Scenario",
strategyNames strategyNames

View File

@@ -1,9 +1,7 @@
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk; using Managing.Domain.Risk;
using Managing.Domain.Strategies;
using Managing.Domain.Synth.Models; using Managing.Domain.Synth.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -223,7 +221,7 @@ public class SynthPredictionService : ISynthPredictionService
/// <param name="isBacktest">Whether this is a backtest scenario</param> /// <param name="isBacktest">Whether this is a backtest scenario</param>
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param> /// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
/// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns> /// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns>
public async Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice, public async Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null) TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null)
{ {
var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig); var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);
@@ -925,7 +923,7 @@ public class SynthPredictionService : ISynthPredictionService
/// Estimates liquidation price based on money management settings /// Estimates liquidation price based on money management settings
/// </summary> /// </summary>
public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction,
MoneyManagement moneyManagement) LightMoneyManagement moneyManagement)
{ {
// This is a simplified estimation - in reality, you'd use the actual money management logic // This is a simplified estimation - in reality, you'd use the actual money management logic
var riskPercentage = 0.02m; // Default 2% risk var riskPercentage = 0.02m; // Default 2% risk

View File

@@ -23,7 +23,7 @@ public class ClosePositionCommandHandler(
var account = await accountService.GetAccount(request.Position.AccountName, false, false); var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null) if (request.Position == null)
{ {
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result; _ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position; return request.Position;
} }
@@ -31,7 +31,7 @@ public class ClosePositionCommandHandler(
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault() ? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow); : await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Check if position still open // Check if position still open
if (!request.IsForBacktest) if (!request.IsForBacktest)
@@ -46,7 +46,7 @@ public class ClosePositionCommandHandler(
request.Position.ProfitAndLoss = request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice, TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
request.Position.Open.Leverage); request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position); await tradingService.UpdatePositionAsync(request.Position);
return request.Position; return request.Position;
} }
} }
@@ -67,7 +67,7 @@ public class ClosePositionCommandHandler(
request.Position.Open.Leverage); request.Position.Open.Leverage);
if (!request.IsForBacktest) if (!request.IsForBacktest)
tradingService.UpdatePosition(request.Position); await tradingService.UpdatePositionAsync(request.Position);
} }
return request.Position; return request.Position;

View File

@@ -1,16 +0,0 @@
using Managing.Common;
using Managing.Domain.Trades;
using MediatR;
namespace Managing.Application.Trading.Commands
{
public class GetPositionsCommand : IRequest<List<Position>>
{
public GetPositionsCommand(Enums.PositionInitiator initiator)
{
Initiator = initiator;
}
public Enums.PositionInitiator Initiator { get; internal set; }
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Domain.MoneyManagements; using Managing.Domain.Trades;
using Managing.Domain.Trades;
using Managing.Domain.Users; using Managing.Domain.Users;
using MediatR; using MediatR;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -10,7 +9,7 @@ namespace Managing.Application.Trading.Commands
{ {
public OpenPositionRequest( public OpenPositionRequest(
string accountName, string accountName,
MoneyManagement moneyManagement, LightMoneyManagement moneyManagement,
TradeDirection direction, TradeDirection direction,
Ticker ticker, Ticker ticker,
PositionInitiator initiator, PositionInitiator initiator,
@@ -43,7 +42,7 @@ namespace Managing.Application.Trading.Commands
public string SignalIdentifier { get; set; } public string SignalIdentifier { get; set; }
public string AccountName { get; } public string AccountName { get; }
public MoneyManagement MoneyManagement { get; } public LightMoneyManagement MoneyManagement { get; }
public TradeDirection Direction { get; } public TradeDirection Direction { get; }
public Ticker Ticker { get; } public Ticker Ticker { get; }
public bool IsForPaperTrading { get; } public bool IsForPaperTrading { get; }

View File

@@ -1,23 +0,0 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Domain.Trades;
using MediatR;
namespace Managing.Application.Trading
{
public class GetPositionsCommandHandler : IRequestHandler<GetPositionsCommand, List<Position>>
{
private readonly ITradingService _tradingService;
public GetPositionsCommandHandler(ITradingService tradingService)
{
_tradingService = tradingService;
}
public Task<List<Position>> Handle(GetPositionsCommand request, CancellationToken cancellationToken)
{
var positions = _tradingService.GetPositions(request.Initiator);
return Task.FromResult(positions.ToList());
}
}
}

View File

@@ -15,9 +15,9 @@ public class GetTradeCommandHandler : IRequestHandler<GetTradeCommand, Trade>
_accountService = accountService; _accountService = accountService;
} }
public Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken) public async Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken)
{ {
var account = _accountService.GetAccount(request.AccountName, true, false).Result; var account = await _accountService.GetAccount(request.AccountName, true, false);
return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker); return await _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
} }
} }

Some files were not shown because too many files have changed in this diff Show More