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
- NET .Core framework
- MongoDb
- PostgreSQL database
- Node.JS
- Discord server with API keys
- Alchemy Keys

View File

@@ -653,3 +653,23 @@ npm run prepare-code
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?
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:
## 🛠️ Usage
```bash
# Fix imports in the src directory (default)
npm run fix-imports
# Development environment
./safe-migrate.sh Development
# Fix imports in a specific directory
npm run fix-imports-dir -- path/to/directory
# Sandbox environment
./safe-migrate.sh Sandbox
# Production environment
./safe-migrate.sh Production
```
Or run the script directly:
## 🔄 Process Flow
```bash
# Fix imports in the src directory (default)
node scripts/add-js-extensions.mjs
1. **Environment Validation**: Validates environment parameter
2. **Connectivity Check**: Tests database connection
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
node scripts/add-js-extensions.mjs path/to/directory
## 🚨 Safety Features
- **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
2. Identifies import statements with relative paths (starting with `./` or `../`) that don't have extensions and adds `.js` extensions
3. Identifies JSON imports that are missing the required assertion and adds `assert { type: 'json' }`
4. Provides a summary of files modified and any errors encountered
- Docker (for PostgreSQL client)
- .NET 8.0 SDK
- Access to target database
### Examples
## 📁 Files
Before:
```javascript
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
- **Backups**: `./backups/managing_[Environment]_backup_[Timestamp].sql`
- **Logs**: `./migration_[Environment]_[Timestamp].log`

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]
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]

View File

@@ -6,10 +6,10 @@ using Managing.Bootstrap;
using Managing.Common;
using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.OpenApi.Models;
using NSwag;
@@ -27,9 +27,9 @@ builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.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 web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
var postgreSqlConnectionString = builder.Configuration.GetSection("PostgreSql")["ConnectionString"];
// Initialize Sentry
SentrySdk.Init(options =>
@@ -61,7 +61,6 @@ builder.Services.AddServiceDiscovery();
// Configure health checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
@@ -91,7 +90,6 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
.WriteTo.Elasticsearch(es);
});
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<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options =>
@@ -108,6 +106,27 @@ builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
}));
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.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document =>
@@ -157,24 +176,6 @@ builder.WebHost.SetupDiscordBot();
var app = builder.Build();
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.UseSwaggerUI(c =>
{

View File

@@ -1,8 +1,4 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"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": {
"Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org",

View File

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

View File

@@ -1,8 +1,4 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "managing-org",
@@ -37,5 +33,6 @@
"WorkerTraderWatcher": false,
"WorkerLeaderboard": 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": {
"Url": "https://influx-db.apps.managing.live",
"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": {
"Url": "http://srv-captain--influx-db:8086/",
"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": {
"Url": "https://influx-db.apps.managing.live",
"Organization": "managing-org",

View File

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

View File

@@ -142,9 +142,9 @@ namespace Managing.Api.Controllers
/// <param name="name">The name of the account to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
[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));
}
}

View File

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

View File

@@ -22,17 +22,16 @@ public abstract class BaseController : ControllerBase
var identity = HttpContext?.User.Identity as ClaimsIdentity;
if (identity != null)
{
var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value;
var user = await _userService.GetUserByAddressAsync(address);
if (user != null)
var address = identity.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
if (address != null)
{
var user = await _userService.GetUserByAddressAsync(address);
return user;
}
throw new Exception("User not found for 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();
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
MoneyManagement moneyManagement;
LightMoneyManagement moneyManagement;
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
{
moneyManagement =
@@ -144,12 +150,6 @@ public class BotController : BaseController
moneyManagement = Map(request.Config.MoneyManagement);
// Format percentage values if using custom money management
moneyManagement?.FormatPercentage();
// Ensure user is set for custom money management
if (moneyManagement != null)
{
moneyManagement.User = user;
}
}
// Validate initialTradingBalance
@@ -425,7 +425,7 @@ public class BotController : BaseController
new StopBotCommand(bot.Identifier));
// Get the saved bot backup
var backup = _botService.GetBotBackup(bot.Identifier);
var backup = await _botService.GetBotBackup(bot.Identifier);
if (backup != null)
{
_botService.StartBotFromBackup(backup);
@@ -564,7 +564,8 @@ public class BotController : BaseController
catch (Exception ex)
{
_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
MoneyManagement moneyManagement = null;
LightMoneyManagement moneyManagement = null;
if (!string.IsNullOrEmpty(request.MoneyManagementName))
{
// Load money management by name
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (moneyManagement == null)
var fullMoneyManagement =
await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (fullMoneyManagement == null)
{
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");
}
// 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)
{
@@ -720,9 +732,6 @@ public class BotController : BaseController
moneyManagement = request.MoneyManagement;
// Format percentage values if using custom money management
moneyManagement.FormatPercentage();
// Ensure user is set for custom money management
moneyManagement.User = user;
}
else
{

View File

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

View File

@@ -40,8 +40,16 @@ public class MoneyManagementController : BaseController
[HttpPost]
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
{
var user = await GetUser();
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement));
try
{
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>
@@ -52,8 +60,16 @@ public class MoneyManagementController : BaseController
[Route("moneymanagements")]
public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements()
{
var user = await GetUser();
return Ok(_moneyManagementService.GetMoneyMangements(user));
try
{
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>
@@ -64,8 +80,22 @@ public class MoneyManagementController : BaseController
[HttpGet]
public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name)
{
var user = await GetUser();
return Ok(await _moneyManagementService.GetMoneyMangement(user, name));
try
{
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>
@@ -76,7 +106,21 @@ public class MoneyManagementController : BaseController
[HttpDelete]
public async Task<ActionResult> DeleteMoneyManagement(string name)
{
var user = await GetUser();
return Ok(_moneyManagementService.DeleteMoneyManagement(user, name));
try
{
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()
{
var user = await GetUser();
var scenarios = _scenarioService.GetScenariosByUser(user);
var scenarios = await _scenarioService.GetScenariosByUserAsync(user);
var scenarioViewModels = scenarios.Select(MapToScenarioViewModel);
return Ok(scenarioViewModels);
}
@@ -59,7 +59,7 @@ public class ScenarioController : BaseController
int? loopbackPeriod = null)
{
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);
return Ok(scenarioViewModel);
}
@@ -73,7 +73,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> DeleteScenario(string name)
{
var user = await GetUser();
return Ok(_scenarioService.DeleteScenarioByUser(user, name));
return Ok(await _scenarioService.DeleteScenarioByUser(user, name));
}
// Update scenario
@@ -81,7 +81,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
{
var user = await GetUser();
return Ok(_scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
return Ok(await _scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
}
/// <summary>
@@ -93,7 +93,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators()
{
var user = await GetUser();
var indicators = _scenarioService.GetIndicatorsByUser(user);
var indicators = await _scenarioService.GetIndicatorsAsync();
var indicatorViewModels = indicators.Select(MapToIndicatorViewModel);
return Ok(indicatorViewModels);
}
@@ -127,7 +127,7 @@ public class ScenarioController : BaseController
int? cyclePeriods = null)
{
var user = await GetUser();
var indicator = _scenarioService.CreateIndicatorForUser(
var indicator = await _scenarioService.CreateIndicatorForUser(
user,
indicatorType,
name,
@@ -153,7 +153,7 @@ public class ScenarioController : BaseController
public async Task<ActionResult> DeleteIndicator(string name)
{
var user = await GetUser();
return Ok(_scenarioService.DeleteIndicatorByUser(user, name));
return Ok(await _scenarioService.DeleteIndicatorByUser(user, name));
}
// Update indicator
@@ -172,7 +172,7 @@ public class ScenarioController : BaseController
int? cyclePeriods = null)
{
var user = await GetUser();
return Ok(_scenarioService.UpdateIndicatorByUser(
return Ok(await _scenarioService.UpdateIndicatorByUser(
user,
indicatorType,
name,

View File

@@ -1,19 +1,15 @@
using Microsoft.AspNetCore.Authorization;
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;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using static Managing.Common.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
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]
[Authorize]
[Route("[controller]")]
@@ -22,28 +18,58 @@ public class SettingsController : BaseController
{
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)
: base(userService)
{
_settingsService = settingsService;
}
/// <summary>
/// Sets up initial application settings.
/// </summary>
/// <returns>A result indicating if the setup was successful.</returns>
[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]
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>
/// Creates default configuration for backtesting including Money Management, Strategy, and Scenario
/// for the authenticated user.
/// </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]
[Route("create-default-config")]
public async Task<ActionResult<bool>> CreateDefaultConfiguration()
@@ -52,9 +78,12 @@ public class SettingsController : BaseController
{
var user = await GetUser();
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)
{

View File

@@ -51,17 +51,6 @@ public class TradingController : BaseController
_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>
/// Retrieves a specific trade by account name, ticker, and exchange order ID.
@@ -98,7 +87,7 @@ public class TradingController : BaseController
[HttpPost("ClosePosition")]
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));
return Ok(result);
}

View File

@@ -11,7 +11,7 @@
</PropertyGroup>
<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.Uris" Version="9.0.0"/>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
@@ -52,8 +52,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Workers\"/>
</ItemGroup>
</Project>

View File

@@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
namespace Managing.Api.Models.Responses
@@ -11,56 +10,67 @@ namespace Managing.Api.Models.Responses
/// <summary>
/// Current status of the bot (Up, Down, etc.)
/// </summary>
[Required] public string Status { get; internal set; }
[Required]
public string Status { get; internal set; }
/// <summary>
/// List of signals generated by the bot
/// </summary>
[Required] public List<Signal> Signals { get; internal set; }
[Required]
public List<LightSignal> Signals { get; internal set; }
/// <summary>
/// List of positions opened by the bot
/// </summary>
[Required] public List<Position> Positions { get; internal set; }
[Required]
public List<Position> Positions { get; internal set; }
/// <summary>
/// Candles used by the bot for analysis
/// </summary>
[Required] public List<Candle> Candles { get; internal set; }
[Required]
public List<Candle> Candles { get; internal set; }
/// <summary>
/// Current win rate percentage
/// </summary>
[Required] public int WinRate { get; internal set; }
[Required]
public int WinRate { get; internal set; }
/// <summary>
/// Current profit and loss
/// </summary>
[Required] public decimal ProfitAndLoss { get; internal set; }
[Required]
public decimal ProfitAndLoss { get; internal set; }
/// <summary>
/// Unique identifier for the bot
/// </summary>
[Required] public string Identifier { get; set; }
[Required]
public string Identifier { get; set; }
/// <summary>
/// Agent name associated with the bot
/// </summary>
[Required] public string AgentName { get; set; }
[Required]
public string AgentName { get; set; }
/// <summary>
/// The full trading bot configuration
/// </summary>
[Required] public TradingBotConfig Config { get; internal set; }
[Required]
public TradingBotConfig Config { get; internal set; }
/// <summary>
/// The time when the bot was created
/// </summary>
[Required] public DateTime CreateDate { get; internal set; }
[Required]
public DateTime CreateDate { get; internal set; }
/// <summary>
/// The time when the bot was started
/// </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.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
@@ -71,7 +72,7 @@ builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks()
.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 web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
@@ -87,9 +88,38 @@ builder.Services.AddHttpClient("GmxHealthCheck")
builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp =>
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
builder.Services.AddHealthChecks()
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
.AddNpgSql(postgreSqlConnectionString, name: "postgresql", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"])
.AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"])
@@ -120,7 +150,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
});
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<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options =>
@@ -209,25 +239,6 @@ if (builder.Configuration.GetValue<bool>("EnableBotManager", false))
// App
var app = builder.Build();
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.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": {
"Url": "http://influxdb:8086/",
"Organization": "",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
using Managing.Domain.MoneyManagements;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions
@@ -8,8 +8,8 @@ namespace Managing.Application.Abstractions
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
Task<MoneyManagement> GetMoneyMangement(User user, string name);
Task<MoneyManagement> GetMoneyMangement(string name);
IEnumerable<MoneyManagement> GetMoneyMangements(User user);
bool DeleteMoneyManagement(User user, string name);
bool DeleteMoneyManagements(User user);
Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user);
Task<bool> DeleteMoneyManagement(User user, string name);
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 InsertAccountAsync(Account account);
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);
IEnumerable<Backtest> GetBacktestsByUser(User user);
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
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");
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,
int pageSize, string sortBy = "score", string sortOrder = "desc");
Backtest GetBacktestByIdForUser(User user, string id);
void DeleteBacktestByIdForUser(User user, string id);
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc");
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 DeleteBacktestsByRequestId(string requestId);
Task DeleteBacktestsByRequestIdAsync(string requestId);
// Bundle backtest methods
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
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
{
Task InsertBotAsync(BotBackup bot);
IEnumerable<BotBackup> GetBots();
Task<IEnumerable<BotBackup>> GetBotsAsync();
Task UpdateBackupBot(BotBackup bot);
Task DeleteBotBackup(string botName);
Task<BotBackup?> GetBotByIdentifierAsync(string identifier);
}

View File

@@ -34,7 +34,7 @@ public interface IGeneticRepository
/// Updates a genetic request
/// </summary>
/// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest);
Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
/// <summary>
/// Deletes a genetic request by ID for a user
@@ -50,8 +50,9 @@ public interface IGeneticRepository
void DeleteAllGeneticRequestsForUser(User user);
/// <summary>
/// Gets all pending genetic requests across all users
/// Gets all genetic requests by status across all users
/// </summary>
/// <returns>Collection of pending genetic requests</returns>
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
/// <param name="status">The status to filter by</param>
/// <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
{
Task<MoneyManagement> GetMoneyManagement(string name);
Task InsertMoneyManagement(MoneyManagement request);
void UpdateMoneyManagement(MoneyManagement moneyManagement);
IEnumerable<MoneyManagement> GetMoneyManagements();
void DeleteMoneyManagement(string name);
void DeleteMoneyManagements();
Task InsertMoneyManagement(LightMoneyManagement request, User user);
Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user);
Task<IEnumerable<MoneyManagement>> GetMoneyManagementsAsync();
Task DeleteMoneyManagementAsync(string name);
Task DeleteMoneyManagementsAsync();
// User-specific operations (these should eventually replace the above methods)
Task<MoneyManagement> GetMoneyManagementByUser(User user, string name);
IEnumerable<MoneyManagement> GetMoneyManagementsByUser(User user);
void DeleteMoneyManagementByUser(User user, string name);
void DeleteMoneyManagementsByUser(User user);
Task<IEnumerable<MoneyManagement>> GetMoneyManagementsByUserAsync(User user);
Task DeleteMoneyManagementByUserAsync(User user, string name);
Task DeleteMoneyManagementsByUserAsync(User user);
}

View File

@@ -6,19 +6,26 @@ public interface IStatisticRepository
{
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
Task<IList<TopVolumeTicker>> GetTopVolumeTickersAsync(DateTime date);
Task SaveSpotligthtOverview(SpotlightOverview overview);
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
Task<IList<SpotlightOverview>> GetSpotlightOverviewsAsync(DateTime date);
void UpdateSpotlightOverview(SpotlightOverview overview);
List<Trader> GetBestTraders();
void UpdateBestTrader(Trader trader);
Task UpdateSpotlightOverviewAsync(SpotlightOverview overview);
Task<List<Trader>> GetBestTradersAsync();
Task UpdateBestTraderAsync(Trader trader);
Task InsertBestTrader(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 RemoveBadTrader(Trader trader);
List<FundingRate> GetFundingRates();
Task<List<FundingRate>> GetFundingRatesAsync();
Task RemoveFundingRate(FundingRate oldRate);
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
{
Scenario GetScenarioByName(string scenario);
void InsertSignal(Signal signal);
IEnumerable<Signal> GetSignalsByUser(User user);
Signal GetSignalByIdentifier(string identifier, User user = null);
void InsertPosition(Position position);
void UpdatePosition(Position position);
Indicator GetStrategyByName(string strategy);
void InsertScenario(Scenario scenario);
void InsertStrategy(Indicator indicator);
IEnumerable<Scenario> GetScenarios();
IEnumerable<Indicator> GetIndicators();
void DeleteScenario(string name);
void DeleteIndicator(string name);
void DeleteScenarios();
void DeleteIndicators();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
Fee GetFee(TradingExchanges exchange);
void InsertFee(Fee fee);
void UpdateFee(Fee fee);
void UpdateScenario(Scenario scenario);
void UpdateStrategy(Indicator indicator);
Task<Scenario> GetScenarioByNameAsync(string scenario);
Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user);
Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null);
Task InsertPositionAsync(Position position);
Task UpdatePositionAsync(Position position);
Task<Indicator> GetStrategyByNameAsync(string strategy);
Task InsertScenarioAsync(Scenario scenario);
Task InsertStrategyAsync(Indicator indicator);
Task<IEnumerable<Scenario>> GetScenariosAsync();
Task<IEnumerable<Indicator>> GetStrategiesAsync();
Task<IEnumerable<Indicator>> GetIndicatorsAsync();
Task DeleteScenarioAsync(string name);
Task DeleteIndicatorAsync(string name);
Task<Position> GetPositionByIdentifierAsync(string identifier);
Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
Task UpdateScenarioAsync(Scenario scenario);
Task UpdateStrategyAsync(Indicator indicator);
}

View File

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

View File

@@ -9,15 +9,29 @@ public interface IAccountService
Task<Account> CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name);
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> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
public Task<Account> GetAccountByUser(User user, string name, 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);
Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
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);
// Additional methods for backtest management
bool DeleteBacktest(string id);
Task<bool> DeleteBacktestAsync(string id);
bool DeleteBacktests();
IEnumerable<Backtest> GetBacktestsByUser(User user);
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
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");
Backtest GetBacktestByIdForUser(User user, string id);
bool DeleteBacktestByUser(User user, string id);
bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
Task<bool> DeleteBacktestByUserAsync(User user, string id);
Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
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");
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
// Bundle backtest methods
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
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<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<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,
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);
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
/// </summary>
/// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest);
Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
/// <summary>
/// Deletes a genetic request by ID for a user
@@ -74,10 +74,11 @@ public interface IGeneticService
void DeleteGeneticRequestByIdForUser(User user, string id);
/// <summary>
/// Gets all pending genetic requests across all users
/// Gets all genetic requests by status across all users
/// </summary>
/// <returns>Collection of pending genetic requests</returns>
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
/// <param name="status">The status to filter by</param>
/// <returns>Collection of genetic requests</returns>
Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status);
/// <summary>
/// Runs the genetic algorithm for a specific request

View File

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

View File

@@ -1,6 +1,4 @@
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Synth.Models;
using static Managing.Common.Enums;
@@ -70,7 +68,7 @@ public interface ISynthPredictionService
/// <param name="isBacktest">Whether this is a backtest</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>
Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice,
Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null);
/// <summary>
@@ -105,5 +103,5 @@ public interface ISynthPredictionService
/// <param name="direction">Position direction</param>
/// <param name="moneyManagement">Money management settings</param>
/// <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
{
Scenario GetScenarioByName(string scenario);
void InsertSignal(Signal signal);
void InsertPosition(Position position);
void UpdatePosition(Position position);
Indicator GetStrategyByName(string strategy);
void InsertScenario(Scenario scenario);
void InsertStrategy(Indicator indicator);
IEnumerable<Scenario> GetScenarios();
IEnumerable<Indicator> GetStrategies();
void DeleteScenario(string name);
void DeleteStrategy(string name);
void DeleteScenarios();
void DeleteStrategies();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositions();
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
Task<Scenario> GetScenarioByNameAsync(string scenario);
Task InsertPositionAsync(Position position);
Task UpdatePositionAsync(Position position);
Task<Indicator> GetStrategyByNameAsync(string strategy);
Task InsertScenarioAsync(Scenario scenario);
Task InsertStrategyAsync(Indicator indicator);
Task<IEnumerable<Scenario>> GetScenariosAsync();
Task<IEnumerable<Indicator>> GetStrategiesAsync();
Task DeleteScenarioAsync(string name);
Task DeleteStrategyAsync(string name);
Task<Position> GetPositionByIdentifierAsync(string identifier);
Task<Position> ManagePosition(Account account, Position position);
void UpdateFee(TradingExchanges evm);
decimal GetFee(Account account, bool isForPaperTrading = false);
Task WatchTrader();
IEnumerable<Trader> GetTradersWatch();
void UpdateDeltaNeutralOpportunities();
void UpdateScenario(Scenario scenario);
void UpdateStrategy(Indicator indicator);
Task<IEnumerable<Trader>> GetTradersWatch();
Task UpdateScenarioAsync(Scenario scenario);
Task UpdateStrategyAsync(Indicator indicator);
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
// Synth API integration methods
Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice,
Task<SignalValidationResult> ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice,
TradingBotConfig botConfig,
bool isBacktest);
@@ -59,7 +51,7 @@ public interface ITradingService
/// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns>
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
Scenario scenario,
List<Candle> candles);
}

View File

@@ -9,5 +9,5 @@ public interface IUserService
Task<User> UpdateAgentName(User user, string agentName);
Task<User> UpdateAvatarUrl(User user, string avatarUrl);
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.Backtesting;
using Managing.Application.Bots.Base;
using Managing.Application.Hubs;
using Managing.Application.ManageBot;
using Managing.Core;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Microsoft.AspNetCore.SignalR;
using Moq;
using Newtonsoft.Json;
using Xunit;
@@ -35,6 +38,8 @@ namespace Managing.Application.Tests
var scenarioService = new Mock<IScenarioService>().Object;
var messengerService = new Mock<IMessengerService>().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 backtestLogger = TradingBaseTests.CreateBacktesterLogger();
var botService = new Mock<IBotService>().Object;
@@ -44,9 +49,9 @@ namespace Managing.Application.Tests
discordService,
_accountService.Object,
_tradingService.Object,
botService);
botService, backupBotService);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
scenarioService, _accountService.Object, messengerService, kaigenService);
scenarioService, _accountService.Object, messengerService, kaigenService, hubContext);
_elapsedTimes = new List<double>();
// Initialize cross-platform file paths

View File

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

View File

@@ -51,7 +51,8 @@ public class PositionTests : BaseTests
Open = openTrade
};
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(
_exchangeService,

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
using System.Text.Json;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -16,22 +16,20 @@ namespace Managing.Application.Workers;
/// </summary>
public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
{
// Removed direct repository usage for bundle requests
private readonly IBacktester _backtester;
private readonly IServiceProvider _serviceProvider;
private readonly IMessengerService _messengerService;
private static readonly WorkerType _workerType = WorkerType.BundleBacktest;
public BundleBacktestWorker(
IBacktester backtester,
IServiceProvider serviceProvider,
IMessengerService messengerService,
ILogger<BundleBacktestWorker> logger,
IWorkerService workerService) : base(
ILogger<BundleBacktestWorker> logger) : base(
_workerType,
logger,
TimeSpan.FromMinutes(1),
workerService)
serviceProvider)
{
_backtester = backtester;
_serviceProvider = serviceProvider;
_messengerService = messengerService;
}
@@ -42,8 +40,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
var processingTasks = new List<Task>();
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
var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending);
var pendingRequests =
await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending);
foreach (var bundleRequest in pendingRequests)
{
@@ -64,9 +67,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
}, cancellationToken);
processingTasks.Add(task);
}
await Task.WhenAll(processingTasks);
await RetryUnfinishedBacktestsInFailedBundles(cancellationToken);
await RetryUnfinishedBacktestsInFailedBundles(backtester, cancellationToken);
}
catch (Exception ex)
{
@@ -77,13 +81,17 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
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
{
_logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);
// Update status to running
bundleRequest.Status = BundleBacktestRequestStatus.Running;
_backtester.UpdateBundleBacktestRequest(bundleRequest);
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
// Deserialize the backtest requests as strongly-typed objects
var backtestRequests =
@@ -105,10 +113,11 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
var runBacktestRequest = backtestRequests[i];
// Update current backtest being processed
bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}";
_backtester.UpdateBundleBacktestRequest(bundleRequest);
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
// 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))
{
bundleRequest.Results.Add(backtestId);
@@ -116,7 +125,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
// Update progress
bundleRequest.CompletedBacktests++;
_backtester.UpdateBundleBacktestRequest(bundleRequest);
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
_logger.LogInformation("Completed backtest {Index} for bundle request {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}",
i + 1, bundleRequest.RequestId);
bundleRequest.FailedBacktests++;
_backtester.UpdateBundleBacktestRequest(bundleRequest);
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
}
}
// Update final status
// Update final status and send notifications
if (bundleRequest.FailedBacktests == 0)
{
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
// Send Telegram message to the user's channelId
if (bundleRequest.User?.TelegramChannel != null)
{
var message = $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) is completed.";
await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
}
await NotifyUser(bundleRequest);
}
else if (bundleRequest.CompletedBacktests == 0)
{
@@ -150,11 +155,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
{
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
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.CurrentBacktest = null;
_backtester.UpdateBundleBacktestRequest(bundleRequest);
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
bundleRequest.RequestId, bundleRequest.Status);
@@ -166,12 +173,22 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
bundleRequest.ErrorMessage = ex.Message;
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
private async Task<string> RunSingleBacktest(RunBacktestRequest runBacktestRequest,
private async Task<string> RunSingleBacktest(IBacktester backtester, RunBacktestRequest runBacktestRequest,
BundleBacktestRequest bundleRequest,
int index, CancellationToken cancellationToken)
{
@@ -259,7 +276,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
};
// Run the backtest (no user context)
var result = await _backtester.RunTradingBotBacktest(
var result = await backtester.RunTradingBotBacktest(
backtestConfig,
runBacktestRequest.StartDate,
runBacktestRequest.EndDate,
@@ -275,9 +292,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
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)
{
if (cancellationToken.IsCancellationRequested)
@@ -292,20 +310,39 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
.Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson);
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;
// If this backtest was not run or did not succeed, re-run it
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))
{
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.Workers.Abstractions;
using Managing.Common;
using Microsoft.Extensions.Logging;
@@ -11,12 +10,12 @@ public class FundingRatesWatcher : BaseWorker<FundingRatesWatcher>
public FundingRatesWatcher(
ILogger<FundingRatesWatcher> logger,
IStatisticService statisticService,
IWorkerService workerService) : base(
IServiceProvider serviceProvider,
IStatisticService statisticService) : base(
Enums.WorkerType.FundingRatesWatcher,
logger,
TimeSpan.FromMinutes(30),
workerService
serviceProvider
)
{
_statisticService = statisticService;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.Logging;
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>
public TraderWatcher(
ILogger<TraderWatcher> logger,
ITradingService tradingService,
IWorkerService workerService) : base(
IServiceProvider serviceProvider,
ITradingService tradingService) : base(
_workerType,
logger,
TimeSpan.FromSeconds(120),
workerService
serviceProvider
)
{
_tradingService = tradingService;

View File

@@ -24,7 +24,7 @@ public class WorkerService : IWorkerService
var worker = new Worker()
{
WorkerType = workerType,
StartTime = DateTime.Now,
StartTime = DateTime.UtcNow,
LastRunTime = null,
ExecutionCount = 0,
Delay = delay
@@ -47,9 +47,9 @@ public class WorkerService : IWorkerService
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)

View File

@@ -12,13 +12,13 @@ namespace Managing.Application.Abstractions
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <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
{
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 AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots();
void StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string identifier);
Task<IEnumerable<BotBackup>> GetSavedBotsAsync();
Task StartBotFromBackup(BotBackup backupBot);
Task<BotBackup> GetBotBackup(string identifier);
/// <summary>
/// Creates a trading bot using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <returns>ITradingBot instance</returns>
ITradingBot CreateTradingBot(TradingBotConfig config);
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
/// <summary>
/// Creates a trading bot for backtesting using the unified TradingBot class
/// </summary>
/// <param name="config">The trading bot configuration</param>
/// <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
ITradingBot CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
Task<ITradingBot> CreateScalpingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config);
Task<ITradingBot> CreateFlippingBot(TradingBotConfig config);
Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config);
IBot CreateSimpleBot(string botName, Workflow workflow);
Task<string> StopBot(string botName);
Task<bool> DeleteBot(string botName);
Task<string> RestartBot(string botName);
void ToggleIsForWatchingOnly(string botName);
Task ToggleIsForWatchingOnly(string botName);
Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig);
}

View File

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

View File

@@ -1,8 +1,10 @@
namespace Managing.Application.Abstractions;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions;
public interface ISettingsService
{
bool SetupSettings();
Task<bool> SetupSettings();
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.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
@@ -16,7 +15,7 @@ namespace Managing.Application.Abstractions
Account Account { get; set; }
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
HashSet<Candle> Candles { get; set; }
HashSet<Signal> Signals { get; set; }
HashSet<LightSignal> Signals { get; set; }
List<Position> Positions { get; set; }
Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
@@ -24,7 +23,7 @@ namespace Managing.Application.Abstractions
DateTime CreateDate { get; }
DateTime PreloadSince { get; set; }
int PreloadedCandlesCount { get; set; }
decimal Fee { get; set; }
Task Run();
Task ToggleIsForWatchOnly();
@@ -36,7 +35,7 @@ namespace Managing.Application.Abstractions
Task LoadAccount();
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);
/// <summary>

View File

@@ -100,9 +100,18 @@ public class AccountService : IAccountService
public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
{
var account = await _accountRepository.GetAccountByNameAsync(name);
if (account == null)
{
throw new ArgumentException($"Account '{name}' not found");
}
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;
}
@@ -123,9 +132,31 @@ public class AccountService : IAccountService
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>();
foreach (var account in result)
@@ -139,15 +170,21 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
{
var cacheKey = $"user-account-{user.Name}";
return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
TimeSpan.FromMinutes(5));
return GetAccountsByUserAsync(user, hideSecrets).Result;
}
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>();
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)
{
var cacheKey = $"user-account-balance-{user.Name}";
var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); },
TimeSpan.FromHours(3));
return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
}
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)
@@ -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
var account = await GetAccountByUser(user, accountName, true, false);
@@ -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
var account = await GetAccountByUser(user, accountName, true, false);

View File

@@ -121,6 +121,12 @@ namespace Managing.Application.Backtesting
result.StartDate = startDate;
result.EndDate = endDate;
// Ensure RequestId is set - required for PostgreSQL NOT NULL constraint
if (string.IsNullOrEmpty(result.RequestId))
{
result.RequestId = Guid.NewGuid().ToString();
}
if (save && user != null)
{
_backtestRepository.InsertBacktestForUser(user, result);
@@ -138,8 +144,9 @@ namespace Managing.Application.Backtesting
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
if (refundSuccess)
{
_logger.LogInformation(
"Successfully refunded credits for user {UserName} after backtest failure", user.Name);
_logger.LogError(
"Successfully refunded credits for user {UserName} after backtest failure: {message}",
user.Name, ex.Message);
}
else
{
@@ -188,7 +195,7 @@ namespace Managing.Application.Backtesting
string requestId = 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
// 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)
{
var accounts = _accountService.GetAccounts(false, false).ToArray();
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
};
return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
}
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}",
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)
{
bot.OptimizedCandles.Enqueue(candle);
bot.Candles.Add(candle);
bot.Run();
await bot.Run();
currentCandle++;
@@ -318,8 +306,6 @@ namespace Managing.Application.Backtesting
var finalPnl = bot.GetProfitAndLoss();
var winRate = bot.GetWinRate();
var optimizedMoneyManagement =
TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement);
var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
var growthPercentage =
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
@@ -357,7 +343,6 @@ namespace Managing.Application.Backtesting
Fees = fees,
WalletBalances = bot.WalletBalances.ToList(),
Statistics = stats,
OptimizedMoneyManagement = optimizedMoneyManagement,
IndicatorsValues = withCandles
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
@@ -442,11 +427,11 @@ namespace Managing.Application.Backtesting
return indicatorsValues;
}
public bool DeleteBacktest(string id)
public async Task<bool> DeleteBacktestAsync(string id)
{
try
{
_backtestRepository.DeleteBacktestByIdForUser(null, id);
await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id);
return true;
}
catch (Exception ex)
@@ -476,12 +461,24 @@ namespace Managing.Application.Backtesting
return backtests;
}
public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
{
var backtests = await _backtestRepository.GetBacktestsByUserAsync(user);
return backtests;
}
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
{
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
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,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
@@ -490,9 +487,19 @@ namespace Managing.Application.Backtesting
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)
return null;
@@ -504,12 +511,12 @@ namespace Managing.Application.Backtesting
var account = new Account
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
var candles = _exchangeService.GetCandlesInflux(
var candles = await _exchangeService.GetCandlesInflux(
account.Exchange,
backtest.Config.Ticker,
backtest.StartDate,
backtest.Config.Timeframe,
backtest.EndDate).Result;
backtest.EndDate);
if (candles != null && candles.Count > 0)
{
@@ -525,11 +532,11 @@ namespace Managing.Application.Backtesting
return backtest;
}
public bool DeleteBacktestByUser(User user, string id)
public async Task<bool> DeleteBacktestByUserAsync(User user, string id)
{
try
{
_backtestRepository.DeleteBacktestByIdForUser(user, id);
await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id);
return true;
}
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
{
_backtestRepository.DeleteBacktestsByIdsForUser(user, ids);
await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids);
return true;
}
catch (Exception ex)
@@ -567,11 +574,11 @@ namespace Managing.Application.Backtesting
}
}
public bool DeleteBacktestsByRequestId(string requestId)
public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
{
try
{
_backtestRepository.DeleteBacktestsByRequestId(requestId);
await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId);
return true;
}
catch (Exception ex)
@@ -589,6 +596,14 @@ namespace Managing.Application.Backtesting
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
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
@@ -600,27 +615,53 @@ namespace Managing.Application.Backtesting
return _backtestRepository.GetBundleBacktestRequestsByUser(user);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
{
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string 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)
{
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
}
public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
{
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
}
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
{
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
}
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
{
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{
// Use the repository method to get all bundles, then filter by status
return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(
BundleBacktestRequestStatus status)
{
return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status);
}
/// <summary>
/// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
/// </summary>

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
@@ -14,6 +15,7 @@ namespace Managing.Application.Bots.Base
private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService;
private readonly IBotService _botService;
private readonly IBackupBotService _backupBotService;
public BotFactory(
IExchangeService exchangeService,
@@ -21,7 +23,8 @@ namespace Managing.Application.Bots.Base
IMessengerService messengerService,
IAccountService accountService,
ITradingService tradingService,
IBotService botService)
IBotService botService,
IBackupBotService backupBotService)
{
_tradingBotLogger = tradingBotLogger;
_exchangeService = exchangeService;
@@ -29,23 +32,24 @@ namespace Managing.Application.Bots.Base
_accountService = accountService;
_tradingService = tradingService;
_botService = botService;
_backupBotService = backupBotService;
}
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
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
return _botService.CreateBacktestTradingBot(config);
return await _botService.CreateBacktestTradingBot(config);
}
}
}

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions;
using Managing.Application.ManageBot;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
@@ -10,13 +11,16 @@ namespace Managing.Application.Bots
{
public readonly ILogger<TradingBot> Logger;
private readonly IBotService _botService;
private readonly IBackupBotService _backupBotService;
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)
{
Logger = logger;
_botService = botService;
_backupBotService = backupBotService;
_workflow = workflow;
Interval = 100;
}
@@ -35,20 +39,20 @@ namespace Managing.Application.Bots
Logger.LogInformation(Identifier);
Logger.LogInformation(DateTime.Now.ToString());
await _workflow.Execute();
SaveBackup();
await SaveBackup();
Logger.LogInformation("__________________________________________________");
});
}
public override void SaveBackup()
public override async Task SaveBackup()
{
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)
{
_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 Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Scenarios;
using Managing.Domain.Users;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -22,6 +24,7 @@ public class GeneticService : IGeneticService
private readonly IBacktester _backtester;
private readonly ILogger<GeneticService> _logger;
private readonly IMessengerService _messengerService;
private readonly IServiceScopeFactory _serviceScopeFactory;
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
@@ -188,12 +191,14 @@ public class GeneticService : IGeneticService
IGeneticRepository geneticRepository,
IBacktester backtester,
ILogger<GeneticService> logger,
IMessengerService messengerService)
IMessengerService messengerService,
IServiceScopeFactory serviceScopeFactory)
{
_geneticRepository = geneticRepository;
_backtester = backtester;
_logger = logger;
_messengerService = messengerService;
_serviceScopeFactory = serviceScopeFactory;
}
public GeneticRequest CreateGeneticRequest(
@@ -247,9 +252,9 @@ public class GeneticService : IGeneticService
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)
@@ -257,9 +262,9 @@ public class GeneticService : IGeneticService
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
}
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
public Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status)
{
return _geneticRepository.GetPendingGeneticRequests();
return _geneticRepository.GetGeneticRequestsAsync(status);
}
/// <summary>
@@ -277,7 +282,7 @@ public class GeneticService : IGeneticService
// Update status to running
request.Status = GeneticRequestStatus.Running;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Create or resume chromosome for trading bot configuration
TradingBotChromosome chromosome;
@@ -307,7 +312,7 @@ public class GeneticService : IGeneticService
}
// Create fitness function first
var fitness = new TradingBotFitness(_backtester, request, _logger);
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
// Create genetic algorithm with better configuration
var ga = new GeneticAlgorithm(
@@ -341,7 +346,7 @@ public class GeneticService : IGeneticService
// Run the genetic algorithm with periodic checks for cancellation
var generationCount = 0;
ga.GenerationRan += (sender, e) =>
ga.GenerationRan += async (sender, e) =>
{
generationCount = ga.GenerationsNumber;
@@ -362,7 +367,7 @@ public class GeneticService : IGeneticService
request.BestChromosome = JsonSerializer.Serialize(geneValues);
}
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Check for cancellation
if (cancellationToken.IsCancellationRequested)
@@ -381,7 +386,7 @@ public class GeneticService : IGeneticService
// Update request status to pending so it can be resumed
request.Status = GeneticRequestStatus.Pending;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
return new GeneticAlgorithmResult
{
@@ -413,7 +418,7 @@ public class GeneticService : IGeneticService
completed_at = DateTime.UtcNow
});
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
// Send notification about the completed genetic algorithm
try
@@ -442,7 +447,7 @@ public class GeneticService : IGeneticService
request.Status = GeneticRequestStatus.Failed;
request.ErrorMessage = ex.Message;
request.CompletedAt = DateTime.UtcNow;
UpdateGeneticRequest(request);
await UpdateGeneticRequestAsync(request);
throw;
}
@@ -505,7 +510,7 @@ public class TradingBotChromosome : ChromosomeBase
private readonly List<IndicatorType> _eligibleIndicators;
private readonly double _maxTakeProfit;
private readonly Random _random = new Random();
private int[]? _indicatorSelectionPattern;
private int[] _indicatorSelectionPattern;
// Gene structure:
// 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
@@ -552,7 +557,7 @@ public class TradingBotChromosome : ChromosomeBase
GenerateIndicatorSelectionPattern();
}
return new Gene(_indicatorSelectionPattern![geneIndex - 5]);
return new Gene(_indicatorSelectionPattern[geneIndex - 5]);
}
else
{
@@ -790,7 +795,7 @@ public class TradingBotChromosome : ChromosomeBase
return _random.Next((int)range.min, (int)range.max + 1);
}
private string? GetParameterName(int index)
private string GetParameterName(int index)
{
return index switch
{
@@ -879,14 +884,14 @@ public class GeneticIndicator
/// </summary>
public class TradingBotFitness : IFitness
{
private readonly IBacktester _backtester;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly GeneticRequest _request;
private GeneticAlgorithm _geneticAlgorithm;
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;
_logger = logger;
}
@@ -909,19 +914,22 @@ public class TradingBotFitness : IFitness
// Get current generation number (default to 0 if not available)
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
// Run backtest
var backtest = _backtester.RunTradingBotBacktest(
config,
_request.StartDate,
_request.EndDate,
_request.User,
true,
false, // Don't include candles
_request.RequestId,
new
{
generation = currentGeneration
}
// Run backtest using scoped service to avoid DbContext concurrency issues
var backtest = ServiceScopeHelpers.WithScopedService<IBacktester, Backtest>(
_serviceScopeFactory,
backtester => backtester.RunTradingBotBacktest(
config,
_request.StartDate,
_request.EndDate,
_request.User,
true,
false, // Don't include candles
_request.RequestId,
new
{
generation = currentGeneration
}
)
).Result;
// Calculate multi-objective fitness based on backtest results
@@ -929,8 +937,9 @@ public class TradingBotFitness : IFitness
return fitness;
}
catch (Exception)
catch (Exception ex)
{
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
// Return low fitness for failed backtests
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.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
@@ -22,13 +22,16 @@ namespace Managing.Application.ManageBot
private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IUserService _userService;
private readonly IBackupBotService _backupBotService;
private readonly IServiceScopeFactory _scopeFactory;
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
new ConcurrentDictionary<string, BotTaskWrapper>();
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
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;
_exchangeService = exchangeService;
@@ -38,35 +41,8 @@ namespace Managing.Application.ManageBot
_tradingService = tradingService;
_moneyManagementService = moneyManagementService;
_userService = userService;
}
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);
}
_backupBotService = backupBotService;
_scopeFactory = scopeFactory;
}
public class BotTaskWrapper
@@ -96,6 +72,27 @@ namespace Managing.Application.ManageBot
_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()
{
var bots = _botTasks.Values
@@ -107,17 +104,17 @@ namespace Managing.Application.ManageBot
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;
Task botTask = null;
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var scalpingBotData = backupBot.Data;
// Get the config directly from the backup
var scalpingConfig = scalpingBotData.Config;
@@ -137,7 +134,7 @@ namespace Managing.Application.ManageBot
// Ensure the scenario is properly loaded from database if needed
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
if (scenario != null)
{
scalpingConfig.Scenario = scenario;
@@ -158,7 +155,7 @@ namespace Managing.Application.ManageBot
// Ensure critical properties are set correctly for restored bots
scalpingConfig.IsForBacktest = false;
bot = CreateTradingBot(scalpingConfig);
bot = await CreateTradingBot(scalpingConfig);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
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);
bot.User = user;
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
bot.LoadBackup(backupBot);
return await _botRepository.GetBotByIdentifierAsync(identifier);
}
// Only start the bot if the backup status is Up
if (backupBot.LastStatus == BotStatus.Up)
public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
{
var backup = await GetBotBackup(identifier);
if (backup != null)
{
// Start the bot asynchronously without waiting for completion
_ = Task.Run(() => bot.Start());
backup.LastStatus = status;
backup.Data = data;
await _botRepository.UpdateBackupBot(backup);
}
else
{
// Keep the bot in Down status if it was originally Down
bot.Stop();
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
Data = data
};
await _botRepository.InsertBotAsync(botBackup);
}
}
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)
@@ -282,12 +288,12 @@ namespace Managing.Application.ManageBot
return BotStatus.Down.ToString();
}
public void ToggleIsForWatchingOnly(string identifier)
public async Task ToggleIsForWatchingOnly(string identifier)
{
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
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
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
if (scenario != null)
{
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
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -386,22 +392,15 @@ namespace Managing.Application.ManageBot
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
}
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestTradingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -418,22 +417,15 @@ namespace Managing.Application.ManageBot
}
config.IsForBacktest = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateScalpingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -450,22 +442,15 @@ namespace Managing.Application.ManageBot
}
config.FlipPosition = false;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -483,22 +468,15 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true;
config.FlipPosition = false;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateFlippingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -515,22 +493,15 @@ namespace Managing.Application.ManageBot
}
config.FlipPosition = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
{
// Ensure the scenario is properly loaded from database if needed
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
{
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
if (scenario != null)
{
config.Scenario = scenario;
@@ -548,14 +519,7 @@ namespace Managing.Application.ManageBot
config.IsForBacktest = true;
config.FlipPosition = true;
return new TradingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
}
}
}

View File

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

View File

@@ -18,9 +18,9 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_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);
var result = new Dictionary<string, BotStatus>();
@@ -42,7 +42,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
_botService.StartBotFromBackup(backupBot);
// 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
int attempts = 0;
@@ -74,7 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
attempts++;
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);
return Task.FromResult(finalStatus.ToString());
return finalStatus.ToString();
}
}

View File

@@ -81,11 +81,11 @@ namespace Managing.Application.ManageBot
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
};
var tradingBot = _botFactory.CreateTradingBot(configToUse);
var tradingBot = await _botFactory.CreateTradingBot(configToUse);
tradingBot.User = request.User;
// Log the configuration being used
await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
_botService.AddTradingBotToCache(tradingBot);
return tradingBot.GetStatus();
@@ -98,7 +98,7 @@ namespace Managing.Application.ManageBot
/// </summary>
/// <param name="bot">The trading bot instance</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
{

View File

@@ -1,35 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Remove="MoneyManagements\Abstractions\**" />
<EmbeddedResource Remove="MoneyManagements\Abstractions\**" />
<None Remove="MoneyManagements\Abstractions\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="MoneyManagements\Abstractions\**"/>
<EmbeddedResource Remove="MoneyManagements\Abstractions\**"/>
<None Remove="MoneyManagements\Abstractions\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.1" />
<PackageReference Include="GeneticSharp" Version="3.1.4" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.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.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Polly" Version="8.4.0" />
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.1"/>
<PackageReference Include="GeneticSharp" Version="3.1.4"/>
<PackageReference Include="MediatR" Version="12.2.0"/>
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.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.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
<PackageReference Include="Polly" Version="8.4.0"/>
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,7 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Strategies;
using Managing.Domain.Synth.Models;
using Microsoft.Extensions.Logging;
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="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>
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)
{
var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);
@@ -925,7 +923,7 @@ public class SynthPredictionService : ISynthPredictionService
/// Estimates liquidation price based on money management settings
/// </summary>
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
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);
if (request.Position == null)
{
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position;
}
@@ -31,7 +31,7 @@ public class ClosePositionCommandHandler(
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? 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
if (!request.IsForBacktest)
@@ -46,7 +46,7 @@ public class ClosePositionCommandHandler(
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position);
await tradingService.UpdatePositionAsync(request.Position);
return request.Position;
}
}
@@ -67,7 +67,7 @@ public class ClosePositionCommandHandler(
request.Position.Open.Leverage);
if (!request.IsForBacktest)
tradingService.UpdatePosition(request.Position);
await tradingService.UpdatePositionAsync(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 MediatR;
using static Managing.Common.Enums;
@@ -10,7 +9,7 @@ namespace Managing.Application.Trading.Commands
{
public OpenPositionRequest(
string accountName,
MoneyManagement moneyManagement,
LightMoneyManagement moneyManagement,
TradeDirection direction,
Ticker ticker,
PositionInitiator initiator,
@@ -43,7 +42,7 @@ namespace Managing.Application.Trading.Commands
public string SignalIdentifier { get; set; }
public string AccountName { get; }
public MoneyManagement MoneyManagement { get; }
public LightMoneyManagement MoneyManagement { get; }
public TradeDirection Direction { get; }
public Ticker Ticker { 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;
}
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;
return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
var account = await _accountService.GetAccount(request.AccountName, true, false);
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