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:
@@ -3,7 +3,7 @@
|
||||
## Requirements
|
||||
|
||||
- NET .Core framework
|
||||
- MongoDb
|
||||
- PostgreSQL database
|
||||
- Node.JS
|
||||
- Discord server with API keys
|
||||
- Alchemy Keys
|
||||
|
||||
20
README.md
20
README.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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
617
scripts/safe-migrate.sh
Executable 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 "=========================================="
|
||||
@@ -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]
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"ManagingDatabase": {
|
||||
"ConnectionString": "mongodb://localhost:27017",
|
||||
"DatabaseName": "ManagingDb"
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://localhost:8086/",
|
||||
"Token": ""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"ManagingDatabase": {
|
||||
"ConnectionString": "mongodb://managingdb:27017",
|
||||
"DatabaseName": "ManagingDb"
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://influxdb:8086/",
|
||||
"Organization": "managing-org",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"ManagingDatabase": {
|
||||
"ConnectionString": "mongodb://managingdb:27017",
|
||||
"DatabaseName": "ManagingDb"
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://influxdb:8086/",
|
||||
"Organization": "",
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
{
|
||||
"ManagingDatabase": {
|
||||
"ConnectionString": "mongodb://managingdb:27017",
|
||||
"DatabaseName": "ManagingDb",
|
||||
"UserName": "admin",
|
||||
"Password": "!MotdepasseFort11"
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://influxdb:8086/",
|
||||
"Organization": "managing-org",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
52
src/Managing.Application/ManageBot/BackupBotService.cs
Normal file
52
src/Managing.Application/ManageBot/BackupBotService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user