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
|
## Requirements
|
||||||
|
|
||||||
- NET .Core framework
|
- NET .Core framework
|
||||||
- MongoDb
|
- PostgreSQL database
|
||||||
- Node.JS
|
- Node.JS
|
||||||
- Discord server with API keys
|
- Discord server with API keys
|
||||||
- Alchemy Keys
|
- Alchemy Keys
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -653,3 +653,23 @@ npm run prepare-code
|
|||||||
|
|
||||||
For more details, see the [scripts documentation](scripts/README.md).
|
For more details, see the [scripts documentation](scripts/README.md).
|
||||||
|
|
||||||
|
# Entity Framework Core Migrations
|
||||||
|
|
||||||
|
To manage database schema changes for the backend, use the following EF Core commands from the project root:
|
||||||
|
|
||||||
|
## Add a Migration
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet ef migrations add <MigrationName> --project src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj --context ManagingDbContext
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update the Database
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet ef database update --project src/Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj --context ManagingDbContext
|
||||||
|
```
|
||||||
|
|
||||||
|
- Replace `<MigrationName>` with your desired migration name.
|
||||||
|
- These commands will build the project and apply migrations to the configured database.
|
||||||
|
- If you drop a table or reset the schema, you may need to remove old migrations and create a new one.
|
||||||
|
|
||||||
|
|||||||
@@ -1,191 +1,63 @@
|
|||||||
# Scripts
|
# 🚀 Safe Database Migration Script
|
||||||
|
|
||||||
This directory contains utility scripts for the project.
|
A clean, focused script for safe database migrations with automatic backup and cleanup.
|
||||||
|
|
||||||
## Add JS Extensions Script
|
## 📋 Features
|
||||||
|
|
||||||
The `add-js-extensions.mjs` script adds `.js` extensions to import statements in JavaScript and TypeScript files, and also adds the required JSON import assertions.
|
- ✅ **Connectivity Check**: Verifies database connection before proceeding
|
||||||
|
- ✅ **Automatic Backup**: Creates backup with retry logic (3 attempts)
|
||||||
|
- ✅ **Safety First**: Exits if backup fails - no migration without backup
|
||||||
|
- ✅ **Smart PostgreSQL**: Uses Docker PostgreSQL client if `psql` not available
|
||||||
|
- ✅ **Migration**: Runs Entity Framework migrations
|
||||||
|
- ✅ **Verification**: Checks migration success
|
||||||
|
- ✅ **Cleanup**: Keeps only last 5 backups per environment
|
||||||
|
|
||||||
### Why This Script?
|
## 🛠️ Usage
|
||||||
|
|
||||||
When working with ES Modules in Node.js:
|
|
||||||
1. Imports of local files require explicit file extensions
|
|
||||||
2. JSON imports require an `assert { type: 'json' }` assertion
|
|
||||||
|
|
||||||
This script automates both processes to ensure your code is ESM-compatible.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Run the script with npm:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Fix imports in the src directory (default)
|
# Development environment
|
||||||
npm run fix-imports
|
./safe-migrate.sh Development
|
||||||
|
|
||||||
# Fix imports in a specific directory
|
# Sandbox environment
|
||||||
npm run fix-imports-dir -- path/to/directory
|
./safe-migrate.sh Sandbox
|
||||||
|
|
||||||
|
# Production environment
|
||||||
|
./safe-migrate.sh Production
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run the script directly:
|
## 🔄 Process Flow
|
||||||
|
|
||||||
```bash
|
1. **Environment Validation**: Validates environment parameter
|
||||||
# Fix imports in the src directory (default)
|
2. **Connectivity Check**: Tests database connection
|
||||||
node scripts/add-js-extensions.mjs
|
3. **Backup Creation**: Creates backup with retry logic
|
||||||
|
4. **Migration**: Runs pending migrations
|
||||||
|
5. **Verification**: Checks migration success
|
||||||
|
6. **Cleanup**: Removes old backups (keeps last 5)
|
||||||
|
|
||||||
# Fix imports in a specific directory
|
## 🚨 Safety Features
|
||||||
node scripts/add-js-extensions.mjs path/to/directory
|
|
||||||
|
- **Mandatory Backup**: Script exits if backup cannot be created
|
||||||
|
- **Retry Logic**: 3 backup attempts with 5-second delays
|
||||||
|
- **Error Handling**: Clear error messages and exit codes
|
||||||
|
- **Logging**: Detailed logs for troubleshooting
|
||||||
|
|
||||||
|
## 📊 Output
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ Database connectivity test passed
|
||||||
|
✅ Database backup created: ./backups/managing_Development_backup_20250726_043047.sql
|
||||||
|
✅ Database migration completed successfully
|
||||||
|
✅ Database schema verification passed
|
||||||
|
✅ Kept last 5 backups for Development environment
|
||||||
```
|
```
|
||||||
|
|
||||||
### What This Script Does
|
## 🔧 Prerequisites
|
||||||
|
|
||||||
1. Recursively scans all JavaScript and TypeScript files in the specified directory
|
- Docker (for PostgreSQL client)
|
||||||
2. Identifies import statements with relative paths (starting with `./` or `../`) that don't have extensions and adds `.js` extensions
|
- .NET 8.0 SDK
|
||||||
3. Identifies JSON imports that are missing the required assertion and adds `assert { type: 'json' }`
|
- Access to target database
|
||||||
4. Provides a summary of files modified and any errors encountered
|
|
||||||
|
|
||||||
### Examples
|
## 📁 Files
|
||||||
|
|
||||||
Before:
|
- **Backups**: `./backups/managing_[Environment]_backup_[Timestamp].sql`
|
||||||
```javascript
|
- **Logs**: `./migration_[Environment]_[Timestamp].log`
|
||||||
import { bigMath } from "./bigmath";
|
|
||||||
import data from "./data.json";
|
|
||||||
```
|
|
||||||
|
|
||||||
After:
|
|
||||||
```javascript
|
|
||||||
import { bigMath } from "./bigmath.js";
|
|
||||||
import data from "./data.json" assert { type: 'json' };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Important Notes
|
|
||||||
|
|
||||||
- The script only modifies imports with relative paths (starting with `./` or `../`)
|
|
||||||
- It skips imports that already have a file extension (except for JSON files)
|
|
||||||
- It adds `.js` extensions to extensionless imports
|
|
||||||
- It adds `assert { type: 'json' }` to JSON imports that don't already have it
|
|
||||||
- It handles regular imports, dynamic imports, and exports
|
|
||||||
|
|
||||||
## Remove JSON Assertions Script
|
|
||||||
|
|
||||||
The `remove-json-assertions.mjs` script removes `assert { type: 'json' }` assertions from JSON imports.
|
|
||||||
|
|
||||||
### Why This Script?
|
|
||||||
|
|
||||||
Different JavaScript environments have different requirements for JSON imports:
|
|
||||||
1. Some newer environments require the `assert { type: 'json' }` assertion
|
|
||||||
2. Others don't support or need these assertions
|
|
||||||
|
|
||||||
This script removes these assertions to improve compatibility with environments that don't need them.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Run the script with npm:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Remove JSON assertions
|
|
||||||
npm run remove-json-assertions
|
|
||||||
|
|
||||||
# Run both import fixes and assertion removal in one command
|
|
||||||
npm run prepare-code
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run the script directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/remove-json-assertions.mjs
|
|
||||||
```
|
|
||||||
|
|
||||||
### What This Script Does
|
|
||||||
|
|
||||||
1. Recursively scans all JavaScript and TypeScript files in the project
|
|
||||||
2. Identifies JSON imports with `assert { type: 'json' }` assertions
|
|
||||||
3. Removes the assertions while preserving the import statements
|
|
||||||
4. Provides a summary of files modified
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
Before:
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json" assert { type: 'json' };
|
|
||||||
```
|
|
||||||
|
|
||||||
After:
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json";
|
|
||||||
```
|
|
||||||
|
|
||||||
### Important Notes
|
|
||||||
|
|
||||||
- The script only modifies JSON imports with assertions
|
|
||||||
- It preserves all other import statements
|
|
||||||
- It works in conjunction with the add-js-extensions script
|
|
||||||
- These scripts can be run in sequence to first fix imports then remove assertions
|
|
||||||
|
|
||||||
## Update JSON Imports Script
|
|
||||||
|
|
||||||
The `update-json-imports.mjs` script updates JSON imports to use the modern `with { type: "json" }` syntax.
|
|
||||||
|
|
||||||
### Why This Script?
|
|
||||||
|
|
||||||
Different JavaScript environments have different requirements for JSON imports:
|
|
||||||
1. Older environments used `assert { type: 'json' }` for JSON imports
|
|
||||||
2. Modern JavaScript environments now use the `with { type: "json" }` syntax
|
|
||||||
|
|
||||||
This script updates your codebase to use the newer, more standardized approach.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Run the script with npm:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update JSON import syntax
|
|
||||||
npm run update-json-imports
|
|
||||||
|
|
||||||
# Run both import fixing and JSON import updating in one command
|
|
||||||
npm run prepare-code
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run the script directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/update-json-imports.mjs
|
|
||||||
```
|
|
||||||
|
|
||||||
### What This Script Does
|
|
||||||
|
|
||||||
1. Recursively scans all JavaScript and TypeScript files in the project
|
|
||||||
2. Identifies JSON imports using either:
|
|
||||||
- The older `assert { type: 'json' }` syntax
|
|
||||||
- No type assertion at all
|
|
||||||
- Erroneous dual syntax (`assert { type: 'json' } with { type: "json" }`)
|
|
||||||
3. Updates them to use the modern `with { type: "json" }` syntax
|
|
||||||
4. Provides a summary of files modified
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
Before (old assert syntax):
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json" assert { type: 'json' };
|
|
||||||
```
|
|
||||||
|
|
||||||
Before (no type assertion):
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json";
|
|
||||||
```
|
|
||||||
|
|
||||||
Before (erroneous dual syntax):
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json" assert { type: 'json' } with { type: "json" };
|
|
||||||
```
|
|
||||||
|
|
||||||
After (in all cases):
|
|
||||||
```javascript
|
|
||||||
import data from "./data.json" with { type: "json" };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Important Notes
|
|
||||||
|
|
||||||
- The script updates all JSON imports to use the modern syntax
|
|
||||||
- It properly fixes cases where both old and new syntax are present
|
|
||||||
- It preserves all other import statements
|
|
||||||
- Files with properly formatted imports are not modified
|
|
||||||
|
|||||||
617
scripts/safe-migrate.sh
Executable file
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]
|
[HttpGet]
|
||||||
public ActionResult<List<Worker>> GetWorkers()
|
public async Task<ActionResult<List<Worker>>> GetWorkers()
|
||||||
{
|
{
|
||||||
return Ok(_workerService.GetWorkers());
|
var workers = await _workerService.GetWorkers();
|
||||||
|
return Ok(workers.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch]
|
[HttpPatch]
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ using Managing.Bootstrap;
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Core.Middleawares;
|
using Managing.Core.Middleawares;
|
||||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
||||||
using Managing.Infrastructure.Databases.MongoDb;
|
using Managing.Infrastructure.Databases.PostgreSql;
|
||||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
using Managing.Infrastructure.Evm.Models.Privy;
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using NSwag;
|
using NSwag;
|
||||||
@@ -27,9 +27,9 @@ builder.Configuration.SetBasePath(AppContext.BaseDirectory);
|
|||||||
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
||||||
|
|
||||||
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"];
|
|
||||||
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
|
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
|
||||||
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
|
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
|
||||||
|
var postgreSqlConnectionString = builder.Configuration.GetSection("PostgreSql")["ConnectionString"];
|
||||||
|
|
||||||
// Initialize Sentry
|
// Initialize Sentry
|
||||||
SentrySdk.Init(options =>
|
SentrySdk.Init(options =>
|
||||||
@@ -61,7 +61,6 @@ builder.Services.AddServiceDiscovery();
|
|||||||
// Configure health checks
|
// Configure health checks
|
||||||
builder.Services.AddHealthChecks()
|
builder.Services.AddHealthChecks()
|
||||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
|
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
|
||||||
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
|
|
||||||
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
|
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
|
||||||
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
|
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
|
||||||
|
|
||||||
@@ -91,7 +90,6 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
|||||||
.WriteTo.Elasticsearch(es);
|
.WriteTo.Elasticsearch(es);
|
||||||
});
|
});
|
||||||
builder.Services.AddOptions();
|
builder.Services.AddOptions();
|
||||||
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
|
|
||||||
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
|
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
|
||||||
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
|
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
|
||||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||||
@@ -108,6 +106,27 @@ builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
|
|||||||
}));
|
}));
|
||||||
builder.Services.AddSignalR().AddJsonProtocol();
|
builder.Services.AddSignalR().AddJsonProtocol();
|
||||||
|
|
||||||
|
// Add PostgreSQL DbContext for worker services
|
||||||
|
builder.Services.AddDbContext<ManagingDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
|
||||||
|
{
|
||||||
|
npgsqlOptions.CommandTimeout(60);
|
||||||
|
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
|
||||||
|
errorCodesToAdd: null);
|
||||||
|
});
|
||||||
|
if (builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
options.EnableDetailedErrors();
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
options.EnableThreadSafetyChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||||
|
options.EnableServiceProviderCaching();
|
||||||
|
options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning);
|
||||||
|
}, ServiceLifetime.Scoped);
|
||||||
|
|
||||||
builder.Services.RegisterWorkersDependencies(builder.Configuration);
|
builder.Services.RegisterWorkersDependencies(builder.Configuration);
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddOpenApiDocument(document =>
|
builder.Services.AddOpenApiDocument(document =>
|
||||||
@@ -157,24 +176,6 @@ builder.WebHost.SetupDiscordBot();
|
|||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
|
|
||||||
// Create MongoDB indexes on startup
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var indexService = app.Services.GetRequiredService<IndexService>();
|
|
||||||
await indexService.CreateIndexesAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Log the error but don't fail the application startup
|
|
||||||
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
|
||||||
logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseOpenApi();
|
app.UseOpenApi();
|
||||||
app.UseSwaggerUI(c =>
|
app.UseSwaggerUI(c =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://localhost:27017",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://localhost:8086/",
|
"Url": "http://localhost:8086/",
|
||||||
"Token": ""
|
"Token": ""
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
"Url": "https://influx-db.apps.managing.live",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://managingdb:27017",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://localhost:27017",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://localhost:8086/",
|
"Url": "http://localhost:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
@@ -37,5 +33,6 @@
|
|||||||
"WorkerTraderWatcher": false,
|
"WorkerTraderWatcher": false,
|
||||||
"WorkerLeaderboard": false,
|
"WorkerLeaderboard": false,
|
||||||
"WorkerFundingRatesWatcher": false,
|
"WorkerFundingRatesWatcher": false,
|
||||||
"WorkerGeneticAlgorithm": true
|
"WorkerGeneticAlgorithm": false,
|
||||||
|
"WorkerBundleBacktest": true
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://root:gRerdhtg546FgrW@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
"Url": "https://influx-db.apps.managing.live",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://srv-captain--influx-db:8086/",
|
"Url": "http://srv-captain--influx-db:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
"Url": "https://influx-db.apps.managing.live",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://managingdb",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "",
|
"Organization": "",
|
||||||
"Token": ""
|
"Token": ""
|
||||||
},
|
},
|
||||||
|
"PostgreSql": {
|
||||||
|
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
|
||||||
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
@@ -17,6 +16,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"N8n": {
|
||||||
|
"WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
|
||||||
|
},
|
||||||
"ElasticConfiguration": {
|
"ElasticConfiguration": {
|
||||||
"Uri": "http://elasticsearch:9200"
|
"Uri": "http://elasticsearch:9200"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -142,9 +142,9 @@ namespace Managing.Api.Controllers
|
|||||||
/// <param name="name">The name of the account to delete.</param>
|
/// <param name="name">The name of the account to delete.</param>
|
||||||
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public ActionResult DeleteAccount(string name)
|
public async Task<ActionResult> DeleteAccount(string name)
|
||||||
{
|
{
|
||||||
var user = GetUser().Result;
|
var user = await GetUser();
|
||||||
return Ok(_AccountService.DeleteAccount(user, name));
|
return Ok(_AccountService.DeleteAccount(user, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ public class BacktestController : BaseController
|
|||||||
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
|
public async Task<ActionResult<IEnumerable<Backtest>>> Backtests()
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_backtester.GetBacktestsByUser(user));
|
var backtests = await _backtester.GetBacktestsByUserAsync(user);
|
||||||
|
return Ok(backtests);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -84,7 +85,7 @@ public class BacktestController : BaseController
|
|||||||
public async Task<ActionResult<Backtest>> Backtest(string id)
|
public async Task<ActionResult<Backtest>> Backtest(string id)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var backtest = _backtester.GetBacktestByIdForUser(user, id);
|
var backtest = await _backtester.GetBacktestByIdForUserAsync(user, id);
|
||||||
|
|
||||||
if (backtest == null)
|
if (backtest == null)
|
||||||
{
|
{
|
||||||
@@ -103,7 +104,8 @@ public class BacktestController : BaseController
|
|||||||
public async Task<ActionResult> DeleteBacktest(string id)
|
public async Task<ActionResult> DeleteBacktest(string id)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_backtester.DeleteBacktestByUser(user, id));
|
var result = await _backtester.DeleteBacktestByUserAsync(user, id);
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -115,7 +117,7 @@ public class BacktestController : BaseController
|
|||||||
public async Task<ActionResult> DeleteBacktests([FromBody] DeleteBacktestsRequest request)
|
public async Task<ActionResult> DeleteBacktests([FromBody] DeleteBacktestsRequest request)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_backtester.DeleteBacktestsByIdsForUser(user, request.BacktestIds));
|
return Ok(await _backtester.DeleteBacktestsByIdsForUserAsync(user, request.BacktestIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -133,7 +135,7 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Request ID is required");
|
return BadRequest("Request ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
var backtests = _backtester.GetBacktestsByRequestId(requestId);
|
var backtests = await _backtester.GetBacktestsByRequestIdAsync(requestId);
|
||||||
return Ok(backtests);
|
return Ok(backtests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +179,7 @@ public class BacktestController : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (backtests, totalCount) =
|
var (backtests, totalCount) =
|
||||||
_backtester.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder);
|
await _backtester.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy, sortOrder);
|
||||||
|
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||||
|
|
||||||
@@ -243,7 +245,7 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Sort order must be 'asc' or 'desc'");
|
return BadRequest("Sort order must be 'asc' or 'desc'");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (backtests, totalCount) = _backtester.GetBacktestsByUserPaginated(user, page, pageSize, sortBy, sortOrder);
|
var (backtests, totalCount) = await _backtester.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||||
|
|
||||||
var response = new PaginatedBacktestsResponse
|
var response = new PaginatedBacktestsResponse
|
||||||
@@ -528,7 +530,7 @@ public class BacktestController : BaseController
|
|||||||
_backtester.DeleteBundleBacktestRequestByIdForUser(user, id);
|
_backtester.DeleteBundleBacktestRequestByIdForUser(user, id);
|
||||||
|
|
||||||
// Then, delete all related backtests
|
// Then, delete all related backtests
|
||||||
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id);
|
var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
@@ -693,7 +695,7 @@ public class BacktestController : BaseController
|
|||||||
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
|
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
|
||||||
|
|
||||||
// Then, delete all related backtests
|
// Then, delete all related backtests
|
||||||
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id);
|
var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,17 +22,16 @@ public abstract class BaseController : ControllerBase
|
|||||||
var identity = HttpContext?.User.Identity as ClaimsIdentity;
|
var identity = HttpContext?.User.Identity as ClaimsIdentity;
|
||||||
if (identity != null)
|
if (identity != null)
|
||||||
{
|
{
|
||||||
var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value;
|
var address = identity.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
|
||||||
var user = await _userService.GetUserByAddressAsync(address);
|
if (address != null)
|
||||||
|
{
|
||||||
if (user != null)
|
var user = await _userService.GetUserByAddressAsync(address);
|
||||||
return user;
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception("User not found for this token");
|
throw new Exception("User not found for this token");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("Not identity assigned to this token");
|
throw new Exception("Not identity assigned to this token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -128,8 +128,14 @@ public class BotController : BaseController
|
|||||||
|
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(user.AgentName))
|
||||||
|
{
|
||||||
|
return BadRequest(
|
||||||
|
"Agent name is required to start a bot. Please configure your agent name in the user profile.");
|
||||||
|
}
|
||||||
|
|
||||||
// Get money management - either by name lookup or use provided object
|
// Get money management - either by name lookup or use provided object
|
||||||
MoneyManagement moneyManagement;
|
LightMoneyManagement moneyManagement;
|
||||||
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
|
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
|
||||||
{
|
{
|
||||||
moneyManagement =
|
moneyManagement =
|
||||||
@@ -144,12 +150,6 @@ public class BotController : BaseController
|
|||||||
moneyManagement = Map(request.Config.MoneyManagement);
|
moneyManagement = Map(request.Config.MoneyManagement);
|
||||||
// Format percentage values if using custom money management
|
// Format percentage values if using custom money management
|
||||||
moneyManagement?.FormatPercentage();
|
moneyManagement?.FormatPercentage();
|
||||||
|
|
||||||
// Ensure user is set for custom money management
|
|
||||||
if (moneyManagement != null)
|
|
||||||
{
|
|
||||||
moneyManagement.User = user;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate initialTradingBalance
|
// Validate initialTradingBalance
|
||||||
@@ -425,7 +425,7 @@ public class BotController : BaseController
|
|||||||
new StopBotCommand(bot.Identifier));
|
new StopBotCommand(bot.Identifier));
|
||||||
|
|
||||||
// Get the saved bot backup
|
// Get the saved bot backup
|
||||||
var backup = _botService.GetBotBackup(bot.Identifier);
|
var backup = await _botService.GetBotBackup(bot.Identifier);
|
||||||
if (backup != null)
|
if (backup != null)
|
||||||
{
|
{
|
||||||
_botService.StartBotFromBackup(backup);
|
_botService.StartBotFromBackup(backup);
|
||||||
@@ -564,7 +564,8 @@ public class BotController : BaseController
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error opening position manually");
|
_logger.LogError(ex, "Error opening position manually");
|
||||||
return StatusCode(500, $"Error opening position: {ex.Message}");
|
return StatusCode(500,
|
||||||
|
$"Error opening position: {ex.Message}, {ex.InnerException?.Message} or {ex.StackTrace}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,20 +700,31 @@ public class BotController : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate and get the money management
|
// Validate and get the money management
|
||||||
MoneyManagement moneyManagement = null;
|
LightMoneyManagement moneyManagement = null;
|
||||||
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||||
{
|
{
|
||||||
// Load money management by name
|
// Load money management by name
|
||||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
var fullMoneyManagement =
|
||||||
if (moneyManagement == null)
|
await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||||
|
if (fullMoneyManagement == null)
|
||||||
{
|
{
|
||||||
return BadRequest($"Money management '{request.MoneyManagementName}' not found");
|
return BadRequest($"Money management '{request.MoneyManagementName}' not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moneyManagement.User?.Name != user.Name)
|
if (fullMoneyManagement.User?.Name != user.Name)
|
||||||
{
|
{
|
||||||
return Forbid("You don't have permission to use this money management");
|
return Forbid("You don't have permission to use this money management");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert to LightMoneyManagement
|
||||||
|
moneyManagement = new LightMoneyManagement
|
||||||
|
{
|
||||||
|
Name = fullMoneyManagement.Name,
|
||||||
|
Timeframe = fullMoneyManagement.Timeframe,
|
||||||
|
StopLoss = fullMoneyManagement.StopLoss,
|
||||||
|
TakeProfit = fullMoneyManagement.TakeProfit,
|
||||||
|
Leverage = fullMoneyManagement.Leverage
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else if (request.MoneyManagement != null)
|
else if (request.MoneyManagement != null)
|
||||||
{
|
{
|
||||||
@@ -720,9 +732,6 @@ public class BotController : BaseController
|
|||||||
moneyManagement = request.MoneyManagement;
|
moneyManagement = request.MoneyManagement;
|
||||||
// Format percentage values if using custom money management
|
// Format percentage values if using custom money management
|
||||||
moneyManagement.FormatPercentage();
|
moneyManagement.FormatPercentage();
|
||||||
|
|
||||||
// Ensure user is set for custom money management
|
|
||||||
moneyManagement.User = user;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -206,14 +206,14 @@ public class DataController : ControllerBase
|
|||||||
/// <returns>A <see cref="SpotlightOverview"/> object containing spotlight data.</returns>
|
/// <returns>A <see cref="SpotlightOverview"/> object containing spotlight data.</returns>
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("Spotlight")]
|
[HttpGet("Spotlight")]
|
||||||
public ActionResult<SpotlightOverview> GetSpotlight()
|
public async Task<ActionResult<SpotlightOverview>> GetSpotlight()
|
||||||
{
|
{
|
||||||
var overview = _cacheService.GetOrSave(nameof(SpotlightOverview),
|
var cacheKey = $"Spotlight_{DateTime.Now.AddDays(-2).ToString("yyyy-MM-dd")}";
|
||||||
() => { return _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2)); }, TimeSpan.FromMinutes(2));
|
var overview = _cacheService.GetValue<SpotlightOverview>(cacheKey);
|
||||||
|
if (overview == null)
|
||||||
if (overview?.Spotlights.Count < overview?.ScenarioCount || overview == null)
|
|
||||||
{
|
{
|
||||||
overview = _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2));
|
overview = await _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2));
|
||||||
|
_cacheService.SaveValue(cacheKey, overview, TimeSpan.FromMinutes(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(overview);
|
return Ok(overview);
|
||||||
@@ -256,7 +256,7 @@ public class DataController : ControllerBase
|
|||||||
{
|
{
|
||||||
// Map ScenarioRequest to domain Scenario object
|
// Map ScenarioRequest to domain Scenario object
|
||||||
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
|
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
|
||||||
indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
|
indicatorsValues = _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new CandlesWithIndicatorsResponse
|
return Ok(new CandlesWithIndicatorsResponse
|
||||||
|
|||||||
@@ -40,8 +40,16 @@ public class MoneyManagementController : BaseController
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
|
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
try
|
||||||
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement));
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var result = await _moneyManagementService.CreateOrUpdateMoneyManagement(user, moneyManagement);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error creating/updating money management: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,8 +60,16 @@ public class MoneyManagementController : BaseController
|
|||||||
[Route("moneymanagements")]
|
[Route("moneymanagements")]
|
||||||
public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements()
|
public async Task<ActionResult<IEnumerable<MoneyManagement>>> GetMoneyManagements()
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
try
|
||||||
return Ok(_moneyManagementService.GetMoneyMangements(user));
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var moneyManagements = await _moneyManagementService.GetMoneyMangements(user);
|
||||||
|
return Ok(moneyManagements);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error retrieving money managements: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -64,8 +80,22 @@ public class MoneyManagementController : BaseController
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name)
|
public async Task<ActionResult<MoneyManagement>> GetMoneyManagement(string name)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
try
|
||||||
return Ok(await _moneyManagementService.GetMoneyMangement(user, name));
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var result = await _moneyManagementService.GetMoneyMangement(user, name);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return NotFound($"Money management strategy '{name}' not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error retrieving money management: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -76,7 +106,21 @@ public class MoneyManagementController : BaseController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult> DeleteMoneyManagement(string name)
|
public async Task<ActionResult> DeleteMoneyManagement(string name)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
try
|
||||||
return Ok(_moneyManagementService.DeleteMoneyManagement(user, name));
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var result = await _moneyManagementService.DeleteMoneyManagement(user, name);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound($"Money management strategy '{name}' not found or could not be deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new { success = true, message = $"Money management strategy '{name}' deleted successfully" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error deleting money management: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ public class ScenarioController : BaseController
|
|||||||
public async Task<ActionResult<IEnumerable<ScenarioViewModel>>> GetScenarios()
|
public async Task<ActionResult<IEnumerable<ScenarioViewModel>>> GetScenarios()
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var scenarios = _scenarioService.GetScenariosByUser(user);
|
var scenarios = await _scenarioService.GetScenariosByUserAsync(user);
|
||||||
var scenarioViewModels = scenarios.Select(MapToScenarioViewModel);
|
var scenarioViewModels = scenarios.Select(MapToScenarioViewModel);
|
||||||
return Ok(scenarioViewModels);
|
return Ok(scenarioViewModels);
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ public class ScenarioController : BaseController
|
|||||||
int? loopbackPeriod = null)
|
int? loopbackPeriod = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var scenario = _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod);
|
var scenario = await _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod);
|
||||||
var scenarioViewModel = MapToScenarioViewModel(scenario);
|
var scenarioViewModel = MapToScenarioViewModel(scenario);
|
||||||
return Ok(scenarioViewModel);
|
return Ok(scenarioViewModel);
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ public class ScenarioController : BaseController
|
|||||||
public async Task<ActionResult> DeleteScenario(string name)
|
public async Task<ActionResult> DeleteScenario(string name)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_scenarioService.DeleteScenarioByUser(user, name));
|
return Ok(await _scenarioService.DeleteScenarioByUser(user, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update scenario
|
// Update scenario
|
||||||
@@ -81,7 +81,7 @@ public class ScenarioController : BaseController
|
|||||||
public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
|
public async Task<ActionResult> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
|
return Ok(await _scenarioService.UpdateScenarioByUser(user, name, strategies, loopbackPeriod));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,7 +93,7 @@ public class ScenarioController : BaseController
|
|||||||
public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators()
|
public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators()
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var indicators = _scenarioService.GetIndicatorsByUser(user);
|
var indicators = await _scenarioService.GetIndicatorsAsync();
|
||||||
var indicatorViewModels = indicators.Select(MapToIndicatorViewModel);
|
var indicatorViewModels = indicators.Select(MapToIndicatorViewModel);
|
||||||
return Ok(indicatorViewModels);
|
return Ok(indicatorViewModels);
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ public class ScenarioController : BaseController
|
|||||||
int? cyclePeriods = null)
|
int? cyclePeriods = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var indicator = _scenarioService.CreateIndicatorForUser(
|
var indicator = await _scenarioService.CreateIndicatorForUser(
|
||||||
user,
|
user,
|
||||||
indicatorType,
|
indicatorType,
|
||||||
name,
|
name,
|
||||||
@@ -153,7 +153,7 @@ public class ScenarioController : BaseController
|
|||||||
public async Task<ActionResult> DeleteIndicator(string name)
|
public async Task<ActionResult> DeleteIndicator(string name)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_scenarioService.DeleteIndicatorByUser(user, name));
|
return Ok(await _scenarioService.DeleteIndicatorByUser(user, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update indicator
|
// Update indicator
|
||||||
@@ -172,7 +172,7 @@ public class ScenarioController : BaseController
|
|||||||
int? cyclePeriods = null)
|
int? cyclePeriods = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
return Ok(_scenarioService.UpdateIndicatorByUser(
|
return Ok(await _scenarioService.UpdateIndicatorByUser(
|
||||||
user,
|
user,
|
||||||
indicatorType,
|
indicatorType,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Managing.Application.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Managing.Application.Abstractions;
|
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Managing.Domain.Strategies;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Managing.Domain.Scenarios;
|
|
||||||
using Managing.Domain.Users;
|
|
||||||
using static Managing.Common.Enums;
|
|
||||||
|
|
||||||
namespace Managing.Api.Controllers;
|
namespace Managing.Api.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controller for managing application settings and configurations.
|
||||||
|
/// Provides endpoints for setting up default configurations and resetting settings.
|
||||||
|
/// Requires authorization for access and produces JSON responses.
|
||||||
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
@@ -22,28 +18,58 @@ public class SettingsController : BaseController
|
|||||||
{
|
{
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SettingsController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingsService">The service for managing application settings.</param>
|
||||||
|
/// <param name="userService">The service for user-related operations.</param>
|
||||||
public SettingsController(ISettingsService settingsService, IUserService userService)
|
public SettingsController(ISettingsService settingsService, IUserService userService)
|
||||||
: base(userService)
|
: base(userService)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up initial application settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A result indicating if the setup was successful.</returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public ActionResult SetupSettings()
|
public async Task<ActionResult<bool>> SetupSettings()
|
||||||
{
|
{
|
||||||
return Ok(_settingsService.SetupSettings());
|
try
|
||||||
|
{
|
||||||
|
var result = await _settingsService.SetupSettings();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error setting up settings: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all application settings to their default values.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A result indicating if the reset was successful.</returns>
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult<bool>> ResetSettings()
|
public async Task<ActionResult<bool>> ResetSettings()
|
||||||
{
|
{
|
||||||
return Ok(await _settingsService.ResetSettings());
|
try
|
||||||
|
{
|
||||||
|
var result = await _settingsService.ResetSettings();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error resetting settings: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates default configuration for backtesting including Money Management, Strategy, and Scenario
|
/// Creates default configuration for backtesting including Money Management, Strategy, and Scenario
|
||||||
|
/// for the authenticated user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A result indicating if the default configuration was created successfully</returns>
|
/// <returns>A result indicating if the default configuration was created successfully.</returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("create-default-config")]
|
[Route("create-default-config")]
|
||||||
public async Task<ActionResult<bool>> CreateDefaultConfiguration()
|
public async Task<ActionResult<bool>> CreateDefaultConfiguration()
|
||||||
@@ -52,9 +78,12 @@ public class SettingsController : BaseController
|
|||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return Unauthorized("User not found");
|
{
|
||||||
|
return Unauthorized("User not found or authentication failed");
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(await _settingsService.CreateDefaultConfiguration(user));
|
var result = await _settingsService.CreateDefaultConfiguration(user);
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,17 +51,6 @@ public class TradingController : BaseController
|
|||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a list of positions based on the initiator type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="positionInitiator">The initiator of the position (e.g., User, System).</param>
|
|
||||||
/// <returns>A list of positions.</returns>
|
|
||||||
[HttpGet("GetPositions")]
|
|
||||||
public async Task<ActionResult<List<Position>>> GetPositions(PositionInitiator positionInitiator)
|
|
||||||
{
|
|
||||||
var result = await _mediator.Send(new GetPositionsCommand(positionInitiator));
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a specific trade by account name, ticker, and exchange order ID.
|
/// Retrieves a specific trade by account name, ticker, and exchange order ID.
|
||||||
@@ -98,7 +87,7 @@ public class TradingController : BaseController
|
|||||||
[HttpPost("ClosePosition")]
|
[HttpPost("ClosePosition")]
|
||||||
public async Task<ActionResult<Position>> ClosePosition(string identifier)
|
public async Task<ActionResult<Position>> ClosePosition(string identifier)
|
||||||
{
|
{
|
||||||
var position = _tradingService.GetPositionByIdentifier(identifier);
|
var position = await _tradingService.GetPositionByIdentifierAsync(identifier);
|
||||||
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
|
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="8.1.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.Npgsql" Version="8.1.0"/>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0"/>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="9.0.0"/>
|
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="9.0.0"/>
|
||||||
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
|
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
|
||||||
@@ -52,8 +52,4 @@
|
|||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Workers\"/>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
|
||||||
namespace Managing.Api.Models.Responses
|
namespace Managing.Api.Models.Responses
|
||||||
@@ -11,56 +10,67 @@ namespace Managing.Api.Models.Responses
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current status of the bot (Up, Down, etc.)
|
/// Current status of the bot (Up, Down, etc.)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public string Status { get; internal set; }
|
[Required]
|
||||||
|
public string Status { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of signals generated by the bot
|
/// List of signals generated by the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public List<Signal> Signals { get; internal set; }
|
[Required]
|
||||||
|
public List<LightSignal> Signals { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of positions opened by the bot
|
/// List of positions opened by the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public List<Position> Positions { get; internal set; }
|
[Required]
|
||||||
|
public List<Position> Positions { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Candles used by the bot for analysis
|
/// Candles used by the bot for analysis
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public List<Candle> Candles { get; internal set; }
|
[Required]
|
||||||
|
public List<Candle> Candles { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current win rate percentage
|
/// Current win rate percentage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public int WinRate { get; internal set; }
|
[Required]
|
||||||
|
public int WinRate { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current profit and loss
|
/// Current profit and loss
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public decimal ProfitAndLoss { get; internal set; }
|
[Required]
|
||||||
|
public decimal ProfitAndLoss { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier for the bot
|
/// Unique identifier for the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public string Identifier { get; set; }
|
[Required]
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Agent name associated with the bot
|
/// Agent name associated with the bot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public string AgentName { get; set; }
|
[Required]
|
||||||
|
public string AgentName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full trading bot configuration
|
/// The full trading bot configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public TradingBotConfig Config { get; internal set; }
|
[Required]
|
||||||
|
public TradingBotConfig Config { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time when the bot was created
|
/// The time when the bot was created
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public DateTime CreateDate { get; internal set; }
|
[Required]
|
||||||
|
public DateTime CreateDate { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time when the bot was started
|
/// The time when the bot was started
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required] public DateTime StartupTime { get; internal set; }
|
[Required]
|
||||||
|
public DateTime StartupTime { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,12 @@ using Managing.Bootstrap;
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Core.Middleawares;
|
using Managing.Core.Middleawares;
|
||||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
||||||
using Managing.Infrastructure.Databases.MongoDb;
|
using Managing.Infrastructure.Databases.PostgreSql;
|
||||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
using Managing.Infrastructure.Evm.Models.Privy;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
@@ -71,7 +72,7 @@ builder.Services.AddServiceDiscovery();
|
|||||||
builder.Services.AddHealthChecks()
|
builder.Services.AddHealthChecks()
|
||||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]);
|
.AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]);
|
||||||
|
|
||||||
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"];
|
var postgreSqlConnectionString = builder.Configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
|
||||||
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
|
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
|
||||||
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
|
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
|
||||||
|
|
||||||
@@ -87,9 +88,38 @@ builder.Services.AddHttpClient("GmxHealthCheck")
|
|||||||
builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp =>
|
builder.Services.AddSingleton<Web3ProxyHealthCheck>(sp =>
|
||||||
new Web3ProxyHealthCheck(sp.GetRequiredService<IHttpClientFactory>(), web3ProxyUrl));
|
new Web3ProxyHealthCheck(sp.GetRequiredService<IHttpClientFactory>(), web3ProxyUrl));
|
||||||
|
|
||||||
|
// Add PostgreSQL DbContext with improved concurrency and connection management
|
||||||
|
builder.Services.AddDbContext<ManagingDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
|
||||||
|
{
|
||||||
|
// Configure connection pooling and timeout settings for better concurrency
|
||||||
|
npgsqlOptions.CommandTimeout(60); // Increase command timeout for complex queries
|
||||||
|
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
|
||||||
|
errorCodesToAdd: null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable detailed errors in development
|
||||||
|
if (builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
options.EnableDetailedErrors();
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
options.EnableThreadSafetyChecks(); // Enable thread safety checks in development
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure query tracking behavior for better performance
|
||||||
|
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Default to no tracking for better performance
|
||||||
|
|
||||||
|
// Enable service provider caching for better performance
|
||||||
|
options.EnableServiceProviderCaching();
|
||||||
|
|
||||||
|
// Enable connection resiliency for backtest and high-load scenarios
|
||||||
|
options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning); // Log warnings for connection issues
|
||||||
|
}, ServiceLifetime.Scoped); // Explicitly specify scoped lifetime for proper request isolation
|
||||||
|
|
||||||
// Add specific health checks for databases and other services
|
// Add specific health checks for databases and other services
|
||||||
builder.Services.AddHealthChecks()
|
builder.Services.AddHealthChecks()
|
||||||
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
|
.AddNpgSql(postgreSqlConnectionString, name: "postgresql", tags: ["database"])
|
||||||
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
|
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
|
||||||
.AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"])
|
.AddCheck<Web3ProxyHealthCheck>("web3proxy", tags: ["api", "external"])
|
||||||
.AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"])
|
.AddCheck<CandleDataHealthCheck>("candle-data", tags: ["database", "candles"])
|
||||||
@@ -120,7 +150,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddOptions();
|
builder.Services.AddOptions();
|
||||||
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
|
builder.Services.Configure<PostgreSqlSettings>(builder.Configuration.GetSection(Constants.Databases.PostgreSql));
|
||||||
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
|
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
|
||||||
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
|
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
|
||||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||||
@@ -209,25 +239,6 @@ if (builder.Configuration.GetValue<bool>("EnableBotManager", false))
|
|||||||
// App
|
// App
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
|
|
||||||
// Create MongoDB indexes on startup
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var indexService = app.Services.GetRequiredService<IndexService>();
|
|
||||||
await indexService.CreateIndexesAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Log the error but don't fail the application startup
|
|
||||||
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
|
||||||
logger.LogError(ex, "Failed to create MongoDB indexes on startup. The application will continue without indexes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseOpenApi();
|
app.UseOpenApi();
|
||||||
app.UseSwaggerUI(c =>
|
app.UseSwaggerUI(c =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "",
|
"Organization": "",
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://managingdb:27017",
|
|
||||||
"DatabaseName": "ManagingDb",
|
|
||||||
"UserName": "admin",
|
|
||||||
"Password": "!MotdepasseFort11"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://localhost:27017",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://localhost:8086/",
|
"Url": "http://localhost:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||||
},
|
},
|
||||||
|
"PostgreSql": {
|
||||||
|
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
|
||||||
|
},
|
||||||
"Privy": {
|
"Privy": {
|
||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
||||||
@@ -39,5 +38,6 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"WorkerBotManager": true,
|
"WorkerBotManager": true,
|
||||||
"WorkerBalancesTracking": true
|
"WorkerBalancesTracking": false,
|
||||||
|
"WorkerNotifyBundleBacktest": true
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
"PostgreSql": {
|
||||||
"ConnectionString": "mongodb://admin:vgRehYTdhghDR@srv-captain--mongo:27017/?authMechanism=SCRAM-SHA-256",
|
"ConnectionString": "Host=apps.prod.live;Port=5432;Database=managing;Username=postgres;Password=postgres"
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
"Url": "https://influx-db.apps.managing.live",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
"PostgreSql": {
|
||||||
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@srv-captain--mongo-db:27017/?authMechanism=SCRAM-SHA-256",
|
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://srv-captain--influx-db:8086/",
|
"Url": "http://srv-captain--influx-db:8086/",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
|
||||||
"ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256",
|
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
"Url": "https://influx-db.apps.managing.live",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
@@ -12,6 +8,9 @@
|
|||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
||||||
},
|
},
|
||||||
|
"PostgreSql": {
|
||||||
|
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
|
||||||
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
@@ -12,9 +12,8 @@
|
|||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6"
|
"Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6"
|
||||||
},
|
},
|
||||||
"ManagingDatabase": {
|
"PostgreSql": {
|
||||||
"ConnectionString": "mongodb://managingdb",
|
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
|
||||||
"DatabaseName": "ManagingDb"
|
|
||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions
|
namespace Managing.Application.Abstractions
|
||||||
@@ -8,8 +8,8 @@ namespace Managing.Application.Abstractions
|
|||||||
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
|
Task<MoneyManagement> CreateOrUpdateMoneyManagement(User user, MoneyManagement request);
|
||||||
Task<MoneyManagement> GetMoneyMangement(User user, string name);
|
Task<MoneyManagement> GetMoneyMangement(User user, string name);
|
||||||
Task<MoneyManagement> GetMoneyMangement(string name);
|
Task<MoneyManagement> GetMoneyMangement(string name);
|
||||||
IEnumerable<MoneyManagement> GetMoneyMangements(User user);
|
Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user);
|
||||||
bool DeleteMoneyManagement(User user, string name);
|
Task<bool> DeleteMoneyManagement(User user, string name);
|
||||||
bool DeleteMoneyManagements(User user);
|
Task<bool> DeleteMoneyManagements(User user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -8,5 +8,5 @@ public interface IAccountRepository
|
|||||||
Task<Account> GetAccountByKeyAsync(string key);
|
Task<Account> GetAccountByKeyAsync(string key);
|
||||||
Task InsertAccountAsync(Account account);
|
Task InsertAccountAsync(Account account);
|
||||||
void DeleteAccountByName(string name);
|
void DeleteAccountByName(string name);
|
||||||
IEnumerable<Account> GetAccounts();
|
Task<IEnumerable<Account>> GetAccountsAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,39 @@ public interface IBacktestRepository
|
|||||||
{
|
{
|
||||||
void InsertBacktestForUser(User user, Backtest result);
|
void InsertBacktestForUser(User user, Backtest result);
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
|
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
||||||
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
||||||
|
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
|
||||||
|
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page,
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page,
|
||||||
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId,
|
||||||
|
int page,
|
||||||
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
|
||||||
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
Backtest GetBacktestByIdForUser(User user, string id);
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page,
|
||||||
void DeleteBacktestByIdForUser(User user, string id);
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
|
|
||||||
|
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
|
||||||
|
Task DeleteBacktestByIdForUserAsync(User user, string id);
|
||||||
|
Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
||||||
void DeleteAllBacktestsForUser(User user);
|
void DeleteAllBacktestsForUser(User user);
|
||||||
void DeleteBacktestsByRequestId(string requestId);
|
Task DeleteBacktestsByRequestIdAsync(string requestId);
|
||||||
|
|
||||||
// Bundle backtest methods
|
// Bundle backtest methods
|
||||||
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
||||||
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
||||||
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
||||||
|
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
|
||||||
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
||||||
|
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
||||||
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
||||||
|
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
||||||
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,8 @@ namespace Managing.Application.Abstractions.Repositories;
|
|||||||
public interface IBotRepository
|
public interface IBotRepository
|
||||||
{
|
{
|
||||||
Task InsertBotAsync(BotBackup bot);
|
Task InsertBotAsync(BotBackup bot);
|
||||||
IEnumerable<BotBackup> GetBots();
|
Task<IEnumerable<BotBackup>> GetBotsAsync();
|
||||||
Task UpdateBackupBot(BotBackup bot);
|
Task UpdateBackupBot(BotBackup bot);
|
||||||
Task DeleteBotBackup(string botName);
|
Task DeleteBotBackup(string botName);
|
||||||
|
Task<BotBackup?> GetBotByIdentifierAsync(string identifier);
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ public interface IGeneticRepository
|
|||||||
/// Updates a genetic request
|
/// Updates a genetic request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="geneticRequest">The genetic request to update</param>
|
/// <param name="geneticRequest">The genetic request to update</param>
|
||||||
void UpdateGeneticRequest(GeneticRequest geneticRequest);
|
Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a genetic request by ID for a user
|
/// Deletes a genetic request by ID for a user
|
||||||
@@ -50,8 +50,9 @@ public interface IGeneticRepository
|
|||||||
void DeleteAllGeneticRequestsForUser(User user);
|
void DeleteAllGeneticRequestsForUser(User user);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all pending genetic requests across all users
|
/// Gets all genetic requests by status across all users
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Collection of pending genetic requests</returns>
|
/// <param name="status">The status to filter by</param>
|
||||||
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
|
/// <returns>Collection of genetic requests</returns>
|
||||||
|
Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status);
|
||||||
}
|
}
|
||||||
@@ -6,15 +6,15 @@ namespace Managing.Application.Abstractions.Repositories;
|
|||||||
public interface ISettingsRepository
|
public interface ISettingsRepository
|
||||||
{
|
{
|
||||||
Task<MoneyManagement> GetMoneyManagement(string name);
|
Task<MoneyManagement> GetMoneyManagement(string name);
|
||||||
Task InsertMoneyManagement(MoneyManagement request);
|
Task InsertMoneyManagement(LightMoneyManagement request, User user);
|
||||||
void UpdateMoneyManagement(MoneyManagement moneyManagement);
|
Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user);
|
||||||
IEnumerable<MoneyManagement> GetMoneyManagements();
|
Task<IEnumerable<MoneyManagement>> GetMoneyManagementsAsync();
|
||||||
void DeleteMoneyManagement(string name);
|
Task DeleteMoneyManagementAsync(string name);
|
||||||
void DeleteMoneyManagements();
|
Task DeleteMoneyManagementsAsync();
|
||||||
|
|
||||||
// User-specific operations (these should eventually replace the above methods)
|
// User-specific operations (these should eventually replace the above methods)
|
||||||
Task<MoneyManagement> GetMoneyManagementByUser(User user, string name);
|
Task<MoneyManagement> GetMoneyManagementByUser(User user, string name);
|
||||||
IEnumerable<MoneyManagement> GetMoneyManagementsByUser(User user);
|
Task<IEnumerable<MoneyManagement>> GetMoneyManagementsByUserAsync(User user);
|
||||||
void DeleteMoneyManagementByUser(User user, string name);
|
Task DeleteMoneyManagementByUserAsync(User user, string name);
|
||||||
void DeleteMoneyManagementsByUser(User user);
|
Task DeleteMoneyManagementsByUserAsync(User user);
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,26 @@ public interface IStatisticRepository
|
|||||||
{
|
{
|
||||||
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
|
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
|
||||||
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
|
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
|
||||||
|
Task<IList<TopVolumeTicker>> GetTopVolumeTickersAsync(DateTime date);
|
||||||
|
|
||||||
Task SaveSpotligthtOverview(SpotlightOverview overview);
|
Task SaveSpotligthtOverview(SpotlightOverview overview);
|
||||||
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
|
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
|
||||||
|
Task<IList<SpotlightOverview>> GetSpotlightOverviewsAsync(DateTime date);
|
||||||
void UpdateSpotlightOverview(SpotlightOverview overview);
|
void UpdateSpotlightOverview(SpotlightOverview overview);
|
||||||
List<Trader> GetBestTraders();
|
Task UpdateSpotlightOverviewAsync(SpotlightOverview overview);
|
||||||
void UpdateBestTrader(Trader trader);
|
|
||||||
|
Task<List<Trader>> GetBestTradersAsync();
|
||||||
|
Task UpdateBestTraderAsync(Trader trader);
|
||||||
Task InsertBestTrader(Trader trader);
|
Task InsertBestTrader(Trader trader);
|
||||||
Task RemoveBestTrader(Trader trader);
|
Task RemoveBestTrader(Trader trader);
|
||||||
List<Trader> GetBadTraders();
|
|
||||||
void UpdateBadTrader(Trader trader);
|
Task<List<Trader>> GetBadTradersAsync();
|
||||||
|
Task UpdateBadTraderAsync(Trader trader);
|
||||||
Task InsertBadTrader(Trader trader);
|
Task InsertBadTrader(Trader trader);
|
||||||
Task RemoveBadTrader(Trader trader);
|
Task RemoveBadTrader(Trader trader);
|
||||||
List<FundingRate> GetFundingRates();
|
|
||||||
|
Task<List<FundingRate>> GetFundingRatesAsync();
|
||||||
Task RemoveFundingRate(FundingRate oldRate);
|
Task RemoveFundingRate(FundingRate oldRate);
|
||||||
Task InsertFundingRate(FundingRate newRate);
|
Task InsertFundingRate(FundingRate newRate);
|
||||||
void UpdateFundingRate(FundingRate oldRate, FundingRate newRate);
|
Task UpdateFundingRateAsync(FundingRate oldRate, FundingRate newRate);
|
||||||
}
|
}
|
||||||
@@ -8,27 +8,23 @@ namespace Managing.Application.Abstractions.Repositories;
|
|||||||
|
|
||||||
public interface ITradingRepository
|
public interface ITradingRepository
|
||||||
{
|
{
|
||||||
Scenario GetScenarioByName(string scenario);
|
Task<Scenario> GetScenarioByNameAsync(string scenario);
|
||||||
void InsertSignal(Signal signal);
|
Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user);
|
||||||
IEnumerable<Signal> GetSignalsByUser(User user);
|
Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null);
|
||||||
Signal GetSignalByIdentifier(string identifier, User user = null);
|
Task InsertPositionAsync(Position position);
|
||||||
void InsertPosition(Position position);
|
Task UpdatePositionAsync(Position position);
|
||||||
void UpdatePosition(Position position);
|
Task<Indicator> GetStrategyByNameAsync(string strategy);
|
||||||
Indicator GetStrategyByName(string strategy);
|
Task InsertScenarioAsync(Scenario scenario);
|
||||||
void InsertScenario(Scenario scenario);
|
Task InsertStrategyAsync(Indicator indicator);
|
||||||
void InsertStrategy(Indicator indicator);
|
Task<IEnumerable<Scenario>> GetScenariosAsync();
|
||||||
IEnumerable<Scenario> GetScenarios();
|
Task<IEnumerable<Indicator>> GetStrategiesAsync();
|
||||||
IEnumerable<Indicator> GetIndicators();
|
Task<IEnumerable<Indicator>> GetIndicatorsAsync();
|
||||||
void DeleteScenario(string name);
|
Task DeleteScenarioAsync(string name);
|
||||||
void DeleteIndicator(string name);
|
Task DeleteIndicatorAsync(string name);
|
||||||
void DeleteScenarios();
|
Task<Position> GetPositionByIdentifierAsync(string identifier);
|
||||||
void DeleteIndicators();
|
Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
|
||||||
Position GetPositionByIdentifier(string identifier);
|
Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
|
||||||
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
|
|
||||||
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
|
Task UpdateScenarioAsync(Scenario scenario);
|
||||||
Fee GetFee(TradingExchanges exchange);
|
Task UpdateStrategyAsync(Indicator indicator);
|
||||||
void InsertFee(Fee fee);
|
|
||||||
void UpdateFee(Fee fee);
|
|
||||||
void UpdateScenario(Scenario scenario);
|
|
||||||
void UpdateStrategy(Indicator indicator);
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ public interface IWorkerRepository
|
|||||||
Task DisableWorker(Enums.WorkerType workerType);
|
Task DisableWorker(Enums.WorkerType workerType);
|
||||||
Task EnableWorker(Enums.WorkerType workerType);
|
Task EnableWorker(Enums.WorkerType workerType);
|
||||||
Task<Worker> GetWorkerAsync(Enums.WorkerType workerType);
|
Task<Worker> GetWorkerAsync(Enums.WorkerType workerType);
|
||||||
IEnumerable<Worker> GetWorkers();
|
Task<IEnumerable<Worker>> GetWorkers();
|
||||||
Task InsertWorker(Worker worker);
|
Task InsertWorker(Worker worker);
|
||||||
Task UpdateWorker(Enums.WorkerType workerType, int executionCount);
|
Task UpdateWorker(Enums.WorkerType workerType, int executionCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,29 @@ public interface IAccountService
|
|||||||
Task<Account> CreateAccount(User user, Account account);
|
Task<Account> CreateAccount(User user, Account account);
|
||||||
bool DeleteAccount(User user, string name);
|
bool DeleteAccount(User user, string name);
|
||||||
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
|
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
|
||||||
IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance);
|
Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true);
|
||||||
|
Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance);
|
||||||
|
Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance);
|
||||||
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
|
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
|
||||||
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
|
public Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
|
||||||
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
|
public Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an account by name directly from the repository.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountName">The name of the account to find</param>
|
||||||
|
/// <param name="hideSecrets">Whether to hide sensitive information</param>
|
||||||
|
/// <param name="getBalance">Whether to fetch the current balance</param>
|
||||||
|
/// <returns>The found account or null if not found</returns>
|
||||||
|
Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false);
|
||||||
|
|
||||||
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
|
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
|
||||||
|
Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
|
||||||
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
|
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
|
||||||
|
|
||||||
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
|
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
|
||||||
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
|
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
|
||||||
|
|
||||||
Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null);
|
Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker,
|
||||||
|
decimal amount, int? chainId = null);
|
||||||
}
|
}
|
||||||
@@ -50,25 +50,34 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
object metadata = null);
|
object metadata = null);
|
||||||
|
|
||||||
// Additional methods for backtest management
|
// Additional methods for backtest management
|
||||||
bool DeleteBacktest(string id);
|
Task<bool> DeleteBacktestAsync(string id);
|
||||||
bool DeleteBacktests();
|
bool DeleteBacktests();
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
|
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
||||||
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
||||||
|
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
Backtest GetBacktestByIdForUser(User user, string id);
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
bool DeleteBacktestByUser(User user, string id);
|
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
|
||||||
bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
|
Task<bool> DeleteBacktestByUserAsync(User user, string id);
|
||||||
|
Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
||||||
bool DeleteBacktestsByUser(User user);
|
bool DeleteBacktestsByUser(User user);
|
||||||
bool DeleteBacktestsByRequestId(string requestId);
|
Task<bool> DeleteBacktestsByRequestIdAsync(string requestId);
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
// Bundle backtest methods
|
// Bundle backtest methods
|
||||||
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
||||||
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
||||||
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
||||||
|
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
|
||||||
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
||||||
|
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
||||||
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
||||||
|
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
||||||
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public interface IExchangeService
|
|||||||
|
|
||||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||||
decimal GetPrice(Account account, Ticker ticker, DateTime date);
|
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
||||||
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||||
|
|
||||||
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval,
|
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval,
|
||||||
@@ -54,7 +54,7 @@ public interface IExchangeService
|
|||||||
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
|
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
|
||||||
Timeframe timeframe, DateTime endDate);
|
Timeframe timeframe, DateTime endDate);
|
||||||
|
|
||||||
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
|
Task<decimal> GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
|
||||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
Orderbook GetOrderbook(Account account, Ticker ticker);
|
||||||
|
|
||||||
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage,
|
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public interface IGeneticService
|
|||||||
/// Updates a genetic request
|
/// Updates a genetic request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="geneticRequest">The genetic request to update</param>
|
/// <param name="geneticRequest">The genetic request to update</param>
|
||||||
void UpdateGeneticRequest(GeneticRequest geneticRequest);
|
Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a genetic request by ID for a user
|
/// Deletes a genetic request by ID for a user
|
||||||
@@ -74,10 +74,11 @@ public interface IGeneticService
|
|||||||
void DeleteGeneticRequestByIdForUser(User user, string id);
|
void DeleteGeneticRequestByIdForUser(User user, string id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all pending genetic requests across all users
|
/// Gets all genetic requests by status across all users
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Collection of pending genetic requests</returns>
|
/// <param name="status">The status to filter by</param>
|
||||||
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
|
/// <returns>Collection of genetic requests</returns>
|
||||||
|
Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the genetic algorithm for a specific request
|
/// Runs the genetic algorithm for a specific request
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ public interface IStatisticService
|
|||||||
int pageSize = 10);
|
int pageSize = 10);
|
||||||
|
|
||||||
List<Trader> GetBadTraders();
|
List<Trader> GetBadTraders();
|
||||||
|
Task<List<Trader>> GetBadTradersAsync();
|
||||||
List<Trader> GetBestTraders();
|
List<Trader> GetBestTraders();
|
||||||
SpotlightOverview GetLastSpotlight(DateTime dateTime);
|
Task<List<Trader>> GetBestTradersAsync();
|
||||||
|
Task<SpotlightOverview> GetLastSpotlight(DateTime dateTime);
|
||||||
|
Task<SpotlightOverview> GetLastSpotlightAsync(DateTime dateTime);
|
||||||
IList<TopVolumeTicker> GetLastTopVolumeTicker();
|
IList<TopVolumeTicker> GetLastTopVolumeTicker();
|
||||||
|
Task<IList<TopVolumeTicker>> GetLastTopVolumeTickerAsync();
|
||||||
Task<List<Trade>> GetLeadboardPositons();
|
Task<List<Trade>> GetLeadboardPositons();
|
||||||
Task<IList<Enums.Ticker>> GetTickers();
|
Task<IList<Enums.Ticker>> GetTickers();
|
||||||
Task UpdateLeaderboard();
|
Task UpdateLeaderboard();
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Synth.Models;
|
using Managing.Domain.Synth.Models;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ public interface ISynthPredictionService
|
|||||||
/// <param name="isBacktest">Whether this is a backtest</param>
|
/// <param name="isBacktest">Whether this is a backtest</param>
|
||||||
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
|
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
|
||||||
/// <returns>Comprehensive signal validation result including confidence, probabilities, and risk analysis</returns>
|
/// <returns>Comprehensive signal validation result including confidence, probabilities, and risk analysis</returns>
|
||||||
Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice,
|
Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
|
||||||
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null);
|
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -105,5 +103,5 @@ public interface ISynthPredictionService
|
|||||||
/// <param name="direction">Position direction</param>
|
/// <param name="direction">Position direction</param>
|
||||||
/// <param name="moneyManagement">Money management settings</param>
|
/// <param name="moneyManagement">Money management settings</param>
|
||||||
/// <returns>Estimated liquidation price</returns>
|
/// <returns>Estimated liquidation price</returns>
|
||||||
decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, MoneyManagement moneyManagement);
|
decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, LightMoneyManagement moneyManagement);
|
||||||
}
|
}
|
||||||
@@ -14,36 +14,28 @@ namespace Managing.Application.Abstractions.Services;
|
|||||||
|
|
||||||
public interface ITradingService
|
public interface ITradingService
|
||||||
{
|
{
|
||||||
Scenario GetScenarioByName(string scenario);
|
Task<Scenario> GetScenarioByNameAsync(string scenario);
|
||||||
void InsertSignal(Signal signal);
|
Task InsertPositionAsync(Position position);
|
||||||
void InsertPosition(Position position);
|
Task UpdatePositionAsync(Position position);
|
||||||
void UpdatePosition(Position position);
|
Task<Indicator> GetStrategyByNameAsync(string strategy);
|
||||||
Indicator GetStrategyByName(string strategy);
|
Task InsertScenarioAsync(Scenario scenario);
|
||||||
void InsertScenario(Scenario scenario);
|
Task InsertStrategyAsync(Indicator indicator);
|
||||||
void InsertStrategy(Indicator indicator);
|
Task<IEnumerable<Scenario>> GetScenariosAsync();
|
||||||
IEnumerable<Scenario> GetScenarios();
|
Task<IEnumerable<Indicator>> GetStrategiesAsync();
|
||||||
IEnumerable<Indicator> GetStrategies();
|
Task DeleteScenarioAsync(string name);
|
||||||
void DeleteScenario(string name);
|
Task DeleteStrategyAsync(string name);
|
||||||
void DeleteStrategy(string name);
|
Task<Position> GetPositionByIdentifierAsync(string identifier);
|
||||||
void DeleteScenarios();
|
|
||||||
void DeleteStrategies();
|
|
||||||
Position GetPositionByIdentifier(string identifier);
|
|
||||||
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
|
|
||||||
IEnumerable<Position> GetPositions();
|
|
||||||
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
|
|
||||||
Task<Position> ManagePosition(Account account, Position position);
|
Task<Position> ManagePosition(Account account, Position position);
|
||||||
void UpdateFee(TradingExchanges evm);
|
|
||||||
decimal GetFee(Account account, bool isForPaperTrading = false);
|
|
||||||
Task WatchTrader();
|
Task WatchTrader();
|
||||||
IEnumerable<Trader> GetTradersWatch();
|
Task<IEnumerable<Trader>> GetTradersWatch();
|
||||||
void UpdateDeltaNeutralOpportunities();
|
Task UpdateScenarioAsync(Scenario scenario);
|
||||||
void UpdateScenario(Scenario scenario);
|
Task UpdateStrategyAsync(Indicator indicator);
|
||||||
void UpdateStrategy(Indicator indicator);
|
|
||||||
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
||||||
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
||||||
|
|
||||||
// Synth API integration methods
|
// Synth API integration methods
|
||||||
Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice,
|
Task<SignalValidationResult> ValidateSynthSignalAsync(LightSignal signal, decimal currentPrice,
|
||||||
TradingBotConfig botConfig,
|
TradingBotConfig botConfig,
|
||||||
bool isBacktest);
|
bool isBacktest);
|
||||||
|
|
||||||
@@ -59,7 +51,7 @@ public interface ITradingService
|
|||||||
/// <param name="scenario">The scenario containing indicators.</param>
|
/// <param name="scenario">The scenario containing indicators.</param>
|
||||||
/// <param name="candles">The candles to calculate indicators for.</param>
|
/// <param name="candles">The candles to calculate indicators for.</param>
|
||||||
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
||||||
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
|
Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValuesAsync(
|
||||||
Scenario scenario,
|
Scenario scenario,
|
||||||
List<Candle> candles);
|
List<Candle> candles);
|
||||||
}
|
}
|
||||||
@@ -9,5 +9,5 @@ public interface IUserService
|
|||||||
Task<User> UpdateAgentName(User user, string agentName);
|
Task<User> UpdateAgentName(User user, string agentName);
|
||||||
Task<User> UpdateAvatarUrl(User user, string avatarUrl);
|
Task<User> UpdateAvatarUrl(User user, string avatarUrl);
|
||||||
Task<User> UpdateTelegramChannel(User user, string telegramChannel);
|
Task<User> UpdateTelegramChannel(User user, string telegramChannel);
|
||||||
User GetUser(string name);
|
Task<User> GetUser(string name);
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,15 @@ using Managing.Application.Abstractions.Repositories;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Backtesting;
|
using Managing.Application.Backtesting;
|
||||||
using Managing.Application.Bots.Base;
|
using Managing.Application.Bots.Base;
|
||||||
|
using Managing.Application.Hubs;
|
||||||
|
using Managing.Application.ManageBot;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -35,6 +38,8 @@ namespace Managing.Application.Tests
|
|||||||
var scenarioService = new Mock<IScenarioService>().Object;
|
var scenarioService = new Mock<IScenarioService>().Object;
|
||||||
var messengerService = new Mock<IMessengerService>().Object;
|
var messengerService = new Mock<IMessengerService>().Object;
|
||||||
var kaigenService = new Mock<IKaigenService>().Object;
|
var kaigenService = new Mock<IKaigenService>().Object;
|
||||||
|
var backupBotService = new Mock<IBackupBotService>().Object;
|
||||||
|
var hubContext = new Mock<IHubContext<BacktestHub>>().Object;
|
||||||
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
||||||
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
||||||
var botService = new Mock<IBotService>().Object;
|
var botService = new Mock<IBotService>().Object;
|
||||||
@@ -44,9 +49,9 @@ namespace Managing.Application.Tests
|
|||||||
discordService,
|
discordService,
|
||||||
_accountService.Object,
|
_accountService.Object,
|
||||||
_tradingService.Object,
|
_tradingService.Object,
|
||||||
botService);
|
botService, backupBotService);
|
||||||
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger,
|
||||||
scenarioService, _accountService.Object, messengerService, kaigenService);
|
scenarioService, _accountService.Object, messengerService, kaigenService, hubContext);
|
||||||
_elapsedTimes = new List<double>();
|
_elapsedTimes = new List<double>();
|
||||||
|
|
||||||
// Initialize cross-platform file paths
|
// Initialize cross-platform file paths
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Strategies.Signals;
|
using Managing.Domain.Strategies.Signals;
|
||||||
using Managing.Domain.Strategies.Trends;
|
using Managing.Domain.Strategies.Trends;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -26,7 +25,7 @@ namespace Managing.Application.Tests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
|
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -39,7 +38,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(rsiStrategy.Signals);
|
resultSignal.AddRange(rsiStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ namespace Managing.Application.Tests
|
|||||||
var account = GetAccount(exchange);
|
var account = GetAccount(exchange);
|
||||||
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
|
var rsiStrategy = new RsiDivergenceIndicator("unittest", 5);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -73,7 +72,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(rsiStrategy.Signals);
|
resultSignal.AddRange(rsiStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,7 @@ namespace Managing.Application.Tests
|
|||||||
var account = GetAccount(exchange);
|
var account = GetAccount(exchange);
|
||||||
var rsiStrategy = new MacdCrossIndicator("unittest", 12, 26, 9);
|
var rsiStrategy = new MacdCrossIndicator("unittest", 12, 26, 9);
|
||||||
var candles = await _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe);
|
var candles = await _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe);
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -100,7 +99,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(rsiStrategy.Signals);
|
resultSignal.AddRange(rsiStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
@@ -114,7 +113,7 @@ namespace Managing.Application.Tests
|
|||||||
var account = GetAccount(exchange);
|
var account = GetAccount(exchange);
|
||||||
var superTrendStrategy = new SuperTrendIndicator("unittest", 10, 3);
|
var superTrendStrategy = new SuperTrendIndicator("unittest", 10, 3);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -127,7 +126,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(superTrendStrategy.Signals);
|
resultSignal.AddRange(superTrendStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
@@ -142,7 +141,7 @@ namespace Managing.Application.Tests
|
|||||||
var chandelierExitStrategy = new ChandelierExitIndicator("unittest", 22, 3);
|
var chandelierExitStrategy = new ChandelierExitIndicator("unittest", 22, 3);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe, false)
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe, false)
|
||||||
.Result;
|
.Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -155,7 +154,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(chandelierExitStrategy.Signals);
|
resultSignal.AddRange(chandelierExitStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
@@ -169,7 +168,7 @@ namespace Managing.Application.Tests
|
|||||||
var account = GetAccount(exchange);
|
var account = GetAccount(exchange);
|
||||||
var emaTrendSrategy = new EmaTrendIndicator("unittest", 200);
|
var emaTrendSrategy = new EmaTrendIndicator("unittest", 200);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
@@ -182,7 +181,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(emaTrendSrategy.Signals);
|
resultSignal.AddRange(emaTrendSrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
@@ -197,7 +196,7 @@ namespace Managing.Application.Tests
|
|||||||
var account = GetAccount(exchange);
|
var account = GetAccount(exchange);
|
||||||
var stochRsiStrategy = new StochRsiTrendIndicator("unittest", 14, 14, 3, 1);
|
var stochRsiStrategy = new StochRsiTrendIndicator("unittest", 14, 14, 3, 1);
|
||||||
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
|
||||||
var resultSignal = new List<Signal>();
|
var resultSignal = new List<LightSignal>();
|
||||||
|
|
||||||
// var json = JsonConvert.SerializeObject(candles);
|
// var json = JsonConvert.SerializeObject(candles);
|
||||||
// File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-candles.json", json);
|
// File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-candles.json", json);
|
||||||
@@ -214,7 +213,7 @@ namespace Managing.Application.Tests
|
|||||||
resultSignal.AddRange(stochRsiStrategy.Signals);
|
resultSignal.AddRange(stochRsiStrategy.Signals);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<List<Signal>>(resultSignal);
|
Assert.IsType<List<LightSignal>>(resultSignal);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
|
||||||
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public class PositionTests : BaseTests
|
|||||||
Open = openTrade
|
Open = openTrade
|
||||||
};
|
};
|
||||||
var command = new ClosePositionCommand(position);
|
var command = new ClosePositionCommand(position);
|
||||||
_ = _tradingService.Setup(m => m.GetPositionByIdentifier(It.IsAny<string>())).Returns(position);
|
_ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny<string>())).ReturnsAsync(position);
|
||||||
|
_ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny<string>())).ReturnsAsync(position);
|
||||||
|
|
||||||
var handler = new ClosePositionCommandHandler(
|
var handler = new ClosePositionCommandHandler(
|
||||||
_exchangeService,
|
_exchangeService,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public interface IWorkerService
|
|||||||
Task DisableWorker(WorkerType workerType);
|
Task DisableWorker(WorkerType workerType);
|
||||||
Task EnableWorker(WorkerType workerType);
|
Task EnableWorker(WorkerType workerType);
|
||||||
Task<Worker> GetWorker(WorkerType workerType);
|
Task<Worker> GetWorker(WorkerType workerType);
|
||||||
IEnumerable<Worker> GetWorkers();
|
Task<IEnumerable<Worker>> GetWorkers();
|
||||||
Task InsertWorker(WorkerType workerType, TimeSpan delay);
|
Task InsertWorker(WorkerType workerType, TimeSpan delay);
|
||||||
Task<bool> ToggleWorker(WorkerType workerType);
|
Task<bool> ToggleWorker(WorkerType workerType);
|
||||||
Task UpdateWorker(WorkerType workerType, int executionCount);
|
Task UpdateWorker(WorkerType workerType, int executionCount);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -7,22 +8,22 @@ namespace Managing.Application.Workers;
|
|||||||
|
|
||||||
public abstract class BaseWorker<T> : BackgroundService where T : class
|
public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly WorkerType _workerType;
|
private readonly WorkerType _workerType;
|
||||||
protected readonly ILogger<T> _logger;
|
protected readonly ILogger<T> _logger;
|
||||||
protected readonly TimeSpan _delay;
|
protected readonly TimeSpan _delay;
|
||||||
private readonly IWorkerService _workerService;
|
|
||||||
private int _executionCount;
|
private int _executionCount;
|
||||||
|
|
||||||
protected BaseWorker(
|
protected BaseWorker(
|
||||||
WorkerType workerType,
|
WorkerType workerType,
|
||||||
ILogger<T> logger,
|
ILogger<T> logger,
|
||||||
TimeSpan timeSpan,
|
TimeSpan timeSpan,
|
||||||
IWorkerService workerService)
|
IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_workerType = workerType;
|
_workerType = workerType;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan;
|
_delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan;
|
||||||
_workerService = workerService;
|
_serviceProvider = serviceProvider;
|
||||||
_executionCount = 0;
|
_executionCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,28 +32,35 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"[{_workerType}] Starting");
|
_logger.LogInformation($"[{_workerType}] Starting");
|
||||||
var worker = await _workerService.GetWorker(_workerType);
|
using (var scope = _serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var workerService = scope.ServiceProvider.GetRequiredService<IWorkerService>();
|
||||||
|
var worker = await workerService.GetWorker(_workerType);
|
||||||
|
|
||||||
if (worker == null)
|
if (worker == null)
|
||||||
{
|
{
|
||||||
await _workerService.InsertWorker(_workerType, _delay);
|
await workerService.InsertWorker(_workerType, _delay);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
|
||||||
$"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
|
_executionCount = worker.ExecutionCount;
|
||||||
_executionCount = worker.ExecutionCount;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping"));
|
cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping"));
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
worker = await _workerService.GetWorker(_workerType);
|
using (var scope = _serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var workerService = scope.ServiceProvider.GetRequiredService<IWorkerService>();
|
||||||
|
var worker = await workerService.GetWorker(_workerType);
|
||||||
|
|
||||||
await Run(cancellationToken);
|
await Run(cancellationToken);
|
||||||
_executionCount++;
|
_executionCount++;
|
||||||
await _workerService.UpdateWorker(_workerType, _executionCount);
|
await workerService.UpdateWorker(_workerType, _executionCount);
|
||||||
|
}
|
||||||
_logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}");
|
_logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}");
|
||||||
await Task.Delay(_delay);
|
await Task.Delay(_delay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -16,22 +16,20 @@ namespace Managing.Application.Workers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
||||||
{
|
{
|
||||||
// Removed direct repository usage for bundle requests
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IBacktester _backtester;
|
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private static readonly WorkerType _workerType = WorkerType.BundleBacktest;
|
private static readonly WorkerType _workerType = WorkerType.BundleBacktest;
|
||||||
|
|
||||||
public BundleBacktestWorker(
|
public BundleBacktestWorker(
|
||||||
IBacktester backtester,
|
IServiceProvider serviceProvider,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
ILogger<BundleBacktestWorker> logger,
|
ILogger<BundleBacktestWorker> logger) : base(
|
||||||
IWorkerService workerService) : base(
|
|
||||||
_workerType,
|
_workerType,
|
||||||
logger,
|
logger,
|
||||||
TimeSpan.FromMinutes(1),
|
TimeSpan.FromMinutes(1),
|
||||||
workerService)
|
serviceProvider)
|
||||||
{
|
{
|
||||||
_backtester = backtester;
|
_serviceProvider = serviceProvider;
|
||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +40,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
var processingTasks = new List<Task>();
|
var processingTasks = new List<Task>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Create a new service scope to get fresh instances of services with scoped DbContext
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
|
||||||
|
|
||||||
// Get pending bundle backtest requests
|
// Get pending bundle backtest requests
|
||||||
var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending);
|
var pendingRequests =
|
||||||
|
await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending);
|
||||||
|
|
||||||
foreach (var bundleRequest in pendingRequests)
|
foreach (var bundleRequest in pendingRequests)
|
||||||
{
|
{
|
||||||
@@ -64,9 +67,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
processingTasks.Add(task);
|
processingTasks.Add(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(processingTasks);
|
await Task.WhenAll(processingTasks);
|
||||||
|
|
||||||
await RetryUnfinishedBacktestsInFailedBundles(cancellationToken);
|
await RetryUnfinishedBacktestsInFailedBundles(backtester, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -77,13 +81,17 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
|
|
||||||
private async Task ProcessBundleRequest(BundleBacktestRequest bundleRequest, CancellationToken cancellationToken)
|
private async Task ProcessBundleRequest(BundleBacktestRequest bundleRequest, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// Create a new service scope for this task to avoid DbContext concurrency issues
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);
|
_logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);
|
||||||
|
|
||||||
// Update status to running
|
// Update status to running
|
||||||
bundleRequest.Status = BundleBacktestRequestStatus.Running;
|
bundleRequest.Status = BundleBacktestRequestStatus.Running;
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
// Deserialize the backtest requests as strongly-typed objects
|
// Deserialize the backtest requests as strongly-typed objects
|
||||||
var backtestRequests =
|
var backtestRequests =
|
||||||
@@ -105,10 +113,11 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
var runBacktestRequest = backtestRequests[i];
|
var runBacktestRequest = backtestRequests[i];
|
||||||
// Update current backtest being processed
|
// Update current backtest being processed
|
||||||
bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}";
|
bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}";
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
// Run the backtest directly with the strongly-typed request
|
// Run the backtest directly with the strongly-typed request
|
||||||
var backtestId = await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken);
|
var backtestId = await RunSingleBacktest(backtester, runBacktestRequest, bundleRequest, i,
|
||||||
|
cancellationToken);
|
||||||
if (!string.IsNullOrEmpty(backtestId))
|
if (!string.IsNullOrEmpty(backtestId))
|
||||||
{
|
{
|
||||||
bundleRequest.Results.Add(backtestId);
|
bundleRequest.Results.Add(backtestId);
|
||||||
@@ -116,7 +125,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
bundleRequest.CompletedBacktests++;
|
bundleRequest.CompletedBacktests++;
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
|
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
|
||||||
i + 1, bundleRequest.RequestId);
|
i + 1, bundleRequest.RequestId);
|
||||||
@@ -126,20 +135,16 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
|
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
|
||||||
i + 1, bundleRequest.RequestId);
|
i + 1, bundleRequest.RequestId);
|
||||||
bundleRequest.FailedBacktests++;
|
bundleRequest.FailedBacktests++;
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update final status
|
// Update final status and send notifications
|
||||||
if (bundleRequest.FailedBacktests == 0)
|
if (bundleRequest.FailedBacktests == 0)
|
||||||
{
|
{
|
||||||
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||||
// Send Telegram message to the user's channelId
|
// Send Telegram message to the user's channelId
|
||||||
if (bundleRequest.User?.TelegramChannel != null)
|
await NotifyUser(bundleRequest);
|
||||||
{
|
|
||||||
var message = $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) is completed.";
|
|
||||||
await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (bundleRequest.CompletedBacktests == 0)
|
else if (bundleRequest.CompletedBacktests == 0)
|
||||||
{
|
{
|
||||||
@@ -150,11 +155,13 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
{
|
{
|
||||||
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||||
bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed";
|
bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed";
|
||||||
|
// Send Telegram message to the user's channelId even with partial failures
|
||||||
|
await NotifyUser(bundleRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
bundleRequest.CompletedAt = DateTime.UtcNow;
|
bundleRequest.CompletedAt = DateTime.UtcNow;
|
||||||
bundleRequest.CurrentBacktest = null;
|
bundleRequest.CurrentBacktest = null;
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
|
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
|
||||||
bundleRequest.RequestId, bundleRequest.Status);
|
bundleRequest.RequestId, bundleRequest.Status);
|
||||||
@@ -166,12 +173,22 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
|
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
|
||||||
bundleRequest.ErrorMessage = ex.Message;
|
bundleRequest.ErrorMessage = ex.Message;
|
||||||
bundleRequest.CompletedAt = DateTime.UtcNow;
|
bundleRequest.CompletedAt = DateTime.UtcNow;
|
||||||
_backtester.UpdateBundleBacktestRequest(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NotifyUser(BundleBacktestRequest bundleRequest)
|
||||||
|
{
|
||||||
|
if (bundleRequest.User?.TelegramChannel != null)
|
||||||
|
{
|
||||||
|
var message =
|
||||||
|
$"⚠️ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed with {bundleRequest.FailedBacktests} failed backtests.";
|
||||||
|
await _messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change RunSingleBacktest to accept RunBacktestRequest directly
|
// Change RunSingleBacktest to accept RunBacktestRequest directly
|
||||||
private async Task<string> RunSingleBacktest(RunBacktestRequest runBacktestRequest,
|
private async Task<string> RunSingleBacktest(IBacktester backtester, RunBacktestRequest runBacktestRequest,
|
||||||
BundleBacktestRequest bundleRequest,
|
BundleBacktestRequest bundleRequest,
|
||||||
int index, CancellationToken cancellationToken)
|
int index, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -259,7 +276,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Run the backtest (no user context)
|
// Run the backtest (no user context)
|
||||||
var result = await _backtester.RunTradingBotBacktest(
|
var result = await backtester.RunTradingBotBacktest(
|
||||||
backtestConfig,
|
backtestConfig,
|
||||||
runBacktestRequest.StartDate,
|
runBacktestRequest.StartDate,
|
||||||
runBacktestRequest.EndDate,
|
runBacktestRequest.EndDate,
|
||||||
@@ -275,9 +292,10 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
return result.Id;
|
return result.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RetryUnfinishedBacktestsInFailedBundles(CancellationToken cancellationToken)
|
private async Task RetryUnfinishedBacktestsInFailedBundles(IBacktester backtester,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var failedBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Failed);
|
var failedBundles = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Failed);
|
||||||
foreach (var failedBundle in failedBundles)
|
foreach (var failedBundle in failedBundles)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
@@ -292,20 +310,39 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
.Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson);
|
.Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson);
|
||||||
if (originalRequests == null) continue;
|
if (originalRequests == null) continue;
|
||||||
|
|
||||||
for (int i = 0; i < originalRequests.Count; i++)
|
for (int i = failedBundle.CompletedBacktests; i < originalRequests.Count; i++)
|
||||||
{
|
{
|
||||||
var expectedId = /* logic to compute expected backtest id for this request */ string.Empty;
|
var expectedId = /* logic to compute expected backtest id for this request */ string.Empty;
|
||||||
// If this backtest was not run or did not succeed, re-run it
|
// If this backtest was not run or did not succeed, re-run it
|
||||||
if (!succeededIds.Contains(expectedId))
|
if (!succeededIds.Contains(expectedId))
|
||||||
{
|
{
|
||||||
var backtestId = await RunSingleBacktest(originalRequests[i], failedBundle, i, cancellationToken);
|
var backtestId = await RunSingleBacktest(backtester, originalRequests[i], failedBundle, i,
|
||||||
|
cancellationToken);
|
||||||
if (!string.IsNullOrEmpty(backtestId))
|
if (!string.IsNullOrEmpty(backtestId))
|
||||||
{
|
{
|
||||||
failedBundle.Results?.Add(backtestId);
|
failedBundle.Results?.Add(backtestId);
|
||||||
_backtester.UpdateBundleBacktestRequest(failedBundle);
|
failedBundle.CompletedBacktests++;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(failedBundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If all backtests succeeded, update the bundle status
|
||||||
|
if (failedBundle.CompletedBacktests == originalRequests.Count)
|
||||||
|
{
|
||||||
|
failedBundle.Status = BundleBacktestRequestStatus.Completed;
|
||||||
|
failedBundle.ErrorMessage = null; // Clear any previous error
|
||||||
|
failedBundle.CompletedAt = DateTime.UtcNow;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(failedBundle);
|
||||||
|
|
||||||
|
// Notify user about successful retry
|
||||||
|
await NotifyUser(failedBundle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Bundle {RequestId} still has unfinished backtests after retry",
|
||||||
|
failedBundle.RequestId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -11,12 +10,12 @@ public class FundingRatesWatcher : BaseWorker<FundingRatesWatcher>
|
|||||||
|
|
||||||
public FundingRatesWatcher(
|
public FundingRatesWatcher(
|
||||||
ILogger<FundingRatesWatcher> logger,
|
ILogger<FundingRatesWatcher> logger,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
Enums.WorkerType.FundingRatesWatcher,
|
Enums.WorkerType.FundingRatesWatcher,
|
||||||
logger,
|
logger,
|
||||||
TimeSpan.FromMinutes(30),
|
TimeSpan.FromMinutes(30),
|
||||||
workerService
|
serviceProvider
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_statisticService = statisticService;
|
_statisticService = statisticService;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -11,18 +12,15 @@ namespace Managing.Application.Workers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
||||||
{
|
{
|
||||||
private readonly IGeneticService _geneticService;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
private readonly IBacktester _backtester;
|
|
||||||
|
|
||||||
public GeneticAlgorithmWorker(
|
public GeneticAlgorithmWorker(
|
||||||
ILogger<GeneticAlgorithmWorker> logger,
|
ILogger<GeneticAlgorithmWorker> logger,
|
||||||
IWorkerService workerService,
|
IServiceProvider serviceProvider,
|
||||||
IGeneticService geneticService,
|
IServiceScopeFactory scopeFactory)
|
||||||
IBacktester backtester)
|
: base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), serviceProvider)
|
||||||
: base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), workerService)
|
|
||||||
{
|
{
|
||||||
_geneticService = geneticService;
|
_scopeFactory = scopeFactory;
|
||||||
_backtester = backtester;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task Run(CancellationToken cancellationToken)
|
protected override async Task Run(CancellationToken cancellationToken)
|
||||||
@@ -48,8 +46,9 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get pending genetic requests from the repository
|
// Get pending genetic requests from the repository using scoped service
|
||||||
var pendingRequests = _geneticService.GetPendingGeneticRequests();
|
var pendingRequests = await ServiceScopeHelpers.WithScopedService<IGeneticService, IEnumerable<GeneticRequest>>(_scopeFactory,
|
||||||
|
async geneticService => await geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Pending));
|
||||||
|
|
||||||
if (!pendingRequests.Any())
|
if (!pendingRequests.Any())
|
||||||
{
|
{
|
||||||
@@ -65,21 +64,24 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId);
|
_logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId);
|
||||||
|
|
||||||
// Update status to Running
|
// Update status to Running using scoped service
|
||||||
request.Status = GeneticRequestStatus.Running;
|
request.Status = GeneticRequestStatus.Running;
|
||||||
_geneticService.UpdateGeneticRequest(request);
|
await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
|
||||||
|
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
|
||||||
|
|
||||||
// Run genetic algorithm using the service
|
// Run genetic algorithm using the service
|
||||||
var results = await _geneticService.RunGeneticAlgorithm(request, cancellationToken);
|
var results = await ServiceScopeHelpers.WithScopedServices<IGeneticService, IBacktester, GeneticAlgorithmResult>(_scopeFactory,
|
||||||
|
async (geneticService, backtester) => await geneticService.RunGeneticAlgorithm(request, cancellationToken));
|
||||||
|
|
||||||
// Update request with results
|
// Update request with results using scoped service
|
||||||
request.Status = GeneticRequestStatus.Completed;
|
request.Status = GeneticRequestStatus.Completed;
|
||||||
request.CompletedAt = DateTime.UtcNow;
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
request.BestFitness = results.BestFitness;
|
request.BestFitness = results.BestFitness;
|
||||||
request.BestIndividual = results.BestIndividual;
|
request.BestIndividual = results.BestIndividual;
|
||||||
request.ProgressInfo = results.ProgressInfo;
|
request.ProgressInfo = results.ProgressInfo;
|
||||||
|
|
||||||
_geneticService.UpdateGeneticRequest(request);
|
await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
|
||||||
|
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
|
||||||
|
|
||||||
_logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId);
|
_logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId);
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,8 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
|||||||
request.Status = GeneticRequestStatus.Failed;
|
request.Status = GeneticRequestStatus.Failed;
|
||||||
request.ErrorMessage = ex.Message;
|
request.ErrorMessage = ex.Message;
|
||||||
request.CompletedAt = DateTime.UtcNow;
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
_geneticService.UpdateGeneticRequest(request);
|
await ServiceScopeHelpers.WithScopedService<IGeneticService>(_scopeFactory,
|
||||||
|
async geneticService => await geneticService.UpdateGeneticRequestAsync(request));
|
||||||
|
|
||||||
_logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId);
|
_logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId);
|
||||||
}
|
}
|
||||||
@@ -100,6 +103,4 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -12,12 +11,12 @@ public class LeaderboardWorker : BaseWorker<LeaderboardWorker>
|
|||||||
|
|
||||||
public LeaderboardWorker(
|
public LeaderboardWorker(
|
||||||
ILogger<LeaderboardWorker> logger,
|
ILogger<LeaderboardWorker> logger,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
_workerType,
|
_workerType,
|
||||||
logger,
|
logger,
|
||||||
TimeSpan.FromHours(24),
|
TimeSpan.FromHours(24),
|
||||||
workerService
|
serviceProvider
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_statisticService = statisticService;
|
_statisticService = statisticService;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
|
|||||||
public PricesBaseWorker(
|
public PricesBaseWorker(
|
||||||
ILogger<T> logger,
|
ILogger<T> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IWorkerService workerService,
|
IServiceProvider serviceProvider,
|
||||||
IStatisticService statisticService,
|
IStatisticService statisticService,
|
||||||
TimeSpan delay,
|
TimeSpan delay,
|
||||||
WorkerType workerType,
|
WorkerType workerType,
|
||||||
@@ -22,7 +22,7 @@ public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
|
|||||||
workerType,
|
workerType,
|
||||||
logger,
|
logger,
|
||||||
delay,
|
delay,
|
||||||
workerService
|
serviceProvider
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_pricesService = pricesService;
|
_pricesService = pricesService;
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class PricesFifteenMinutesWorker : PricesBaseWorker<PricesFifteenMinutesW
|
|||||||
public PricesFifteenMinutesWorker(
|
public PricesFifteenMinutesWorker(
|
||||||
ILogger<PricesFifteenMinutesWorker> logger,
|
ILogger<PricesFifteenMinutesWorker> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
logger,
|
logger,
|
||||||
pricesService,
|
pricesService,
|
||||||
workerService,
|
serviceProvider,
|
||||||
statisticService,
|
statisticService,
|
||||||
TimeSpan.FromMinutes(1),
|
TimeSpan.FromMinutes(1),
|
||||||
WorkerType.PriceFifteenMinutes,
|
WorkerType.PriceFifteenMinutes,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class PricesFiveMinutesWorker : PricesBaseWorker<PricesFiveMinutesWorker>
|
|||||||
public PricesFiveMinutesWorker(
|
public PricesFiveMinutesWorker(
|
||||||
ILogger<PricesFiveMinutesWorker> logger,
|
ILogger<PricesFiveMinutesWorker> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
logger,
|
logger,
|
||||||
pricesService,
|
pricesService,
|
||||||
workerService,
|
serviceProvider,
|
||||||
statisticService,
|
statisticService,
|
||||||
TimeSpan.FromMinutes(1),
|
TimeSpan.FromMinutes(1),
|
||||||
WorkerType.PriceFiveMinutes,
|
WorkerType.PriceFiveMinutes,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class PricesFourHoursWorker : PricesBaseWorker<PricesFourHoursWorker>
|
|||||||
public PricesFourHoursWorker(
|
public PricesFourHoursWorker(
|
||||||
ILogger<PricesFourHoursWorker> logger,
|
ILogger<PricesFourHoursWorker> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
logger,
|
logger,
|
||||||
pricesService,
|
pricesService,
|
||||||
workerService,
|
serviceProvider,
|
||||||
statisticService,
|
statisticService,
|
||||||
TimeSpan.FromHours(2),
|
TimeSpan.FromHours(2),
|
||||||
WorkerType.PriceFourHour,
|
WorkerType.PriceFourHour,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class PricesOneDayWorker : PricesBaseWorker<PricesOneDayWorker>
|
|||||||
public PricesOneDayWorker(
|
public PricesOneDayWorker(
|
||||||
ILogger<PricesOneDayWorker> logger,
|
ILogger<PricesOneDayWorker> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
logger,
|
logger,
|
||||||
pricesService,
|
pricesService,
|
||||||
workerService,
|
serviceProvider,
|
||||||
statisticService,
|
statisticService,
|
||||||
TimeSpan.FromHours(12),
|
TimeSpan.FromHours(12),
|
||||||
WorkerType.PriceOneDay,
|
WorkerType.PriceOneDay,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class PricesOneHourWorker : PricesBaseWorker<PricesOneHourWorker>
|
|||||||
public PricesOneHourWorker(
|
public PricesOneHourWorker(
|
||||||
ILogger<PricesOneHourWorker> logger,
|
ILogger<PricesOneHourWorker> logger,
|
||||||
IPricesService pricesService,
|
IPricesService pricesService,
|
||||||
IStatisticService statisticService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
IStatisticService statisticService) : base(
|
||||||
logger,
|
logger,
|
||||||
pricesService,
|
pricesService,
|
||||||
workerService,
|
serviceProvider,
|
||||||
statisticService,
|
statisticService,
|
||||||
TimeSpan.FromMinutes(30),
|
TimeSpan.FromMinutes(30),
|
||||||
WorkerType.PriceOneHour,
|
WorkerType.PriceOneHour,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -30,7 +31,10 @@ public class PricesService : IPricesService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
|
var account = new Account()
|
||||||
|
{
|
||||||
|
Exchange = exchange,
|
||||||
|
};
|
||||||
|
|
||||||
if (account == null)
|
if (account == null)
|
||||||
throw new Exception($"Enable to found account for exchange {exchange}");
|
throw new Exception($"Enable to found account for exchange {exchange}");
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -11,12 +10,12 @@ public class SpotlightWorker : BaseWorker<SpotlightWorker>
|
|||||||
|
|
||||||
public SpotlightWorker(
|
public SpotlightWorker(
|
||||||
ILogger<SpotlightWorker> logger,
|
ILogger<SpotlightWorker> logger,
|
||||||
IWorkerService workerService,
|
IServiceProvider serviceProvider,
|
||||||
IStatisticService statisticService) : base(
|
IStatisticService statisticService) : base(
|
||||||
Enums.WorkerType.Spotlight,
|
Enums.WorkerType.Spotlight,
|
||||||
logger,
|
logger,
|
||||||
TimeSpan.FromMinutes(5),
|
TimeSpan.FromMinutes(5),
|
||||||
workerService)
|
serviceProvider)
|
||||||
{
|
{
|
||||||
_statisticService = statisticService;
|
_statisticService = statisticService;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Managing.Domain.MoneyManagements;
|
|||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -55,7 +54,7 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top)
|
public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top)
|
||||||
{
|
{
|
||||||
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
|
var account = (await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == exchange);
|
||||||
var date = DateTime.UtcNow;
|
var date = DateTime.UtcNow;
|
||||||
|
|
||||||
if (account == null)
|
if (account == null)
|
||||||
@@ -73,7 +72,7 @@ public class StatisticService : IStatisticService
|
|||||||
foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker)))
|
foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker)))
|
||||||
{
|
{
|
||||||
var volume = _exchangeService.GetVolume(account, ticker);
|
var volume = _exchangeService.GetVolume(account, ticker);
|
||||||
var price = _exchangeService.GetPrice(account, ticker, date);
|
var price = await _exchangeService.GetPrice(account, ticker, date);
|
||||||
volumeTickers.Add(ticker, volume * price);
|
volumeTickers.Add(ticker, volume * price);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +137,7 @@ public class StatisticService : IStatisticService
|
|||||||
if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 5m)
|
if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 5m)
|
||||||
{
|
{
|
||||||
await _messengerService.SendFundingRateUpdate(oldRate, newRate);
|
await _messengerService.SendFundingRateUpdate(oldRate, newRate);
|
||||||
_statisticRepository.UpdateFundingRate(oldRate, newRate);
|
await _statisticRepository.UpdateFundingRateAsync(oldRate, newRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,16 +150,21 @@ public class StatisticService : IStatisticService
|
|||||||
oldRate.Direction == newRate.Direction;
|
oldRate.Direction == newRate.Direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<FundingRate>> GetFundingRates()
|
public async Task<List<FundingRate>> GetFundingRates()
|
||||||
{
|
{
|
||||||
var previousFundingRate = _statisticRepository.GetFundingRates();
|
var previousFundingRate = await _statisticRepository.GetFundingRatesAsync();
|
||||||
return Task.FromResult(previousFundingRate);
|
return previousFundingRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<TopVolumeTicker> GetLastTopVolumeTicker()
|
public IList<TopVolumeTicker> GetLastTopVolumeTicker()
|
||||||
|
{
|
||||||
|
return GetLastTopVolumeTickerAsync().Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IList<TopVolumeTicker>> GetLastTopVolumeTickerAsync()
|
||||||
{
|
{
|
||||||
var from = DateTime.UtcNow.AddDays(-1);
|
var from = DateTime.UtcNow.AddDays(-1);
|
||||||
return _statisticRepository.GetTopVolumeTickers(from);
|
return await _statisticRepository.GetTopVolumeTickersAsync(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<Ticker>> GetTickers()
|
public async Task<IList<Ticker>> GetTickers()
|
||||||
@@ -179,13 +183,14 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
public async Task UpdateSpotlight()
|
public async Task UpdateSpotlight()
|
||||||
{
|
{
|
||||||
var scenarios = _tradingService.GetScenarios();
|
var scenarios = await _tradingService.GetScenariosAsync();
|
||||||
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm);
|
var account =
|
||||||
|
(await _accountService.GetAccounts(false, false)).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm);
|
||||||
|
|
||||||
if (account == null)
|
if (account == null)
|
||||||
throw new Exception($"Enable to found default account");
|
throw new Exception($"Enable to found default account");
|
||||||
|
|
||||||
var overview = GetLastSpotlight(DateTime.Now.AddMinutes(-20));
|
var overview = await GetLastSpotlightAsync(DateTime.UtcNow.AddMinutes(-20));
|
||||||
|
|
||||||
if (overview != null)
|
if (overview != null)
|
||||||
{
|
{
|
||||||
@@ -205,7 +210,7 @@ public class StatisticService : IStatisticService
|
|||||||
overview = new SpotlightOverview
|
overview = new SpotlightOverview
|
||||||
{
|
{
|
||||||
Spotlights = new List<Spotlight>(),
|
Spotlights = new List<Spotlight>(),
|
||||||
DateTime = DateTime.Now,
|
DateTime = DateTime.UtcNow,
|
||||||
Identifier = Guid.NewGuid(),
|
Identifier = Guid.NewGuid(),
|
||||||
ScenarioCount = scenarios.Count(),
|
ScenarioCount = scenarios.Count(),
|
||||||
};
|
};
|
||||||
@@ -225,33 +230,43 @@ public class StatisticService : IStatisticService
|
|||||||
Scenario = scenario
|
Scenario = scenario
|
||||||
};
|
};
|
||||||
|
|
||||||
var options = new ParallelOptions()
|
// Use SemaphoreSlim to limit concurrency to 2 operations at a time
|
||||||
{
|
using var semaphore = new SemaphoreSlim(2, 2);
|
||||||
MaxDegreeOfParallelism = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = Parallel.ForEach(tickers, options, async ticker =>
|
var tickerTasks = tickers.Select(async ticker =>
|
||||||
{
|
{
|
||||||
spotlight.TickerSignals.Add(new TickerSignal
|
await semaphore.WaitAsync();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Ticker = ticker,
|
var tickerSignal = new TickerSignal
|
||||||
FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
|
{
|
||||||
FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
|
Ticker = ticker,
|
||||||
OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour),
|
FiveMinutes = await GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
|
||||||
FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour),
|
FifteenMinutes = await GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
|
||||||
OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay)
|
OneHour = await GetSignals(account, scenario, ticker, Timeframe.OneHour),
|
||||||
});
|
FourHour = await GetSignals(account, scenario, ticker, Timeframe.FourHour),
|
||||||
|
OneDay = await GetSignals(account, scenario, ticker, Timeframe.OneDay)
|
||||||
|
};
|
||||||
|
return tickerSignal;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var tickerSignals = await Task.WhenAll(tickerTasks);
|
||||||
|
spotlight.TickerSignals.AddRange(tickerSignals.Where(ts => ts != null));
|
||||||
|
|
||||||
overview.Spotlights.Add(spotlight);
|
overview.Spotlights.Add(spotlight);
|
||||||
_statisticRepository.UpdateSpotlightOverview(overview);
|
await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
|
||||||
}
|
}
|
||||||
|
|
||||||
overview.DateTime = DateTime.Now;
|
overview.DateTime = DateTime.UtcNow;
|
||||||
_statisticRepository.UpdateSpotlightOverview(overview);
|
await _statisticRepository.UpdateSpotlightOverviewAsync(overview);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<Signal>> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe)
|
private async Task<List<LightSignal>> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -284,8 +299,8 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
var backtest = await _backtester.RunTradingBotBacktest(
|
var backtest = await _backtester.RunTradingBotBacktest(
|
||||||
config,
|
config,
|
||||||
DateTime.Now.AddDays(-7),
|
DateTime.UtcNow.AddDays(-7),
|
||||||
DateTime.Now,
|
DateTime.UtcNow,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false);
|
false);
|
||||||
@@ -300,9 +315,14 @@ public class StatisticService : IStatisticService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpotlightOverview GetLastSpotlight(DateTime dateTime)
|
public async Task<SpotlightOverview> GetLastSpotlight(DateTime dateTime)
|
||||||
{
|
{
|
||||||
var overviews = _statisticRepository.GetSpotlightOverviews(dateTime);
|
return await GetLastSpotlightAsync(dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SpotlightOverview> GetLastSpotlightAsync(DateTime dateTime)
|
||||||
|
{
|
||||||
|
var overviews = await _statisticRepository.GetSpotlightOverviewsAsync(dateTime);
|
||||||
|
|
||||||
if (overviews.Any())
|
if (overviews.Any())
|
||||||
{
|
{
|
||||||
@@ -314,17 +334,27 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
public List<Trader> GetBestTraders()
|
public List<Trader> GetBestTraders()
|
||||||
{
|
{
|
||||||
return _statisticRepository.GetBestTraders();
|
return GetBestTradersAsync().Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Trader>> GetBestTradersAsync()
|
||||||
|
{
|
||||||
|
return await _statisticRepository.GetBestTradersAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Trader> GetBadTraders()
|
public List<Trader> GetBadTraders()
|
||||||
{
|
{
|
||||||
return _statisticRepository.GetBadTraders();
|
return GetBadTradersAsync().Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Trader>> GetBadTradersAsync()
|
||||||
|
{
|
||||||
|
return await _statisticRepository.GetBadTradersAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Trade>> GetLeadboardPositons()
|
public async Task<List<Trade>> GetLeadboardPositons()
|
||||||
{
|
{
|
||||||
var customWatchAccount = _tradingService.GetTradersWatch();
|
var customWatchAccount = await _tradingService.GetTradersWatch();
|
||||||
var trades = new List<Trade>();
|
var trades = new List<Trade>();
|
||||||
|
|
||||||
foreach (var trader in customWatchAccount)
|
foreach (var trader in customWatchAccount)
|
||||||
@@ -337,7 +367,7 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
public async Task UpdateLeaderboard()
|
public async Task UpdateLeaderboard()
|
||||||
{
|
{
|
||||||
var previousBestTraders = _statisticRepository.GetBestTraders();
|
var previousBestTraders = await _statisticRepository.GetBestTradersAsync();
|
||||||
var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader();
|
var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader();
|
||||||
|
|
||||||
// Update / Insert best trader
|
// Update / Insert best trader
|
||||||
@@ -345,7 +375,7 @@ public class StatisticService : IStatisticService
|
|||||||
{
|
{
|
||||||
if (previousBestTraders.Exists((p) => p.Address == trader.Address))
|
if (previousBestTraders.Exists((p) => p.Address == trader.Address))
|
||||||
{
|
{
|
||||||
_statisticRepository.UpdateBestTrader(trader);
|
await _statisticRepository.UpdateBestTraderAsync(trader);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -367,7 +397,7 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
public async Task UpdateNoobiesboard()
|
public async Task UpdateNoobiesboard()
|
||||||
{
|
{
|
||||||
var previousBadTraders = _statisticRepository.GetBadTraders();
|
var previousBadTraders = await _statisticRepository.GetBadTradersAsync();
|
||||||
var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader();
|
var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader();
|
||||||
|
|
||||||
// Update / Insert best trader
|
// Update / Insert best trader
|
||||||
@@ -375,7 +405,7 @@ public class StatisticService : IStatisticService
|
|||||||
{
|
{
|
||||||
if (previousBadTraders.Exists((p) => p.Address == trader.Address))
|
if (previousBadTraders.Exists((p) => p.Address == trader.Address))
|
||||||
{
|
{
|
||||||
_statisticRepository.UpdateBadTrader(trader);
|
await _statisticRepository.UpdateBadTraderAsync(trader);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -22,12 +21,12 @@ public class TraderWatcher : BaseWorker<TraderWatcher>
|
|||||||
/// <param name="workerService">The worker service to manage worker lifecycle.</param>
|
/// <param name="workerService">The worker service to manage worker lifecycle.</param>
|
||||||
public TraderWatcher(
|
public TraderWatcher(
|
||||||
ILogger<TraderWatcher> logger,
|
ILogger<TraderWatcher> logger,
|
||||||
ITradingService tradingService,
|
IServiceProvider serviceProvider,
|
||||||
IWorkerService workerService) : base(
|
ITradingService tradingService) : base(
|
||||||
_workerType,
|
_workerType,
|
||||||
logger,
|
logger,
|
||||||
TimeSpan.FromSeconds(120),
|
TimeSpan.FromSeconds(120),
|
||||||
workerService
|
serviceProvider
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class WorkerService : IWorkerService
|
|||||||
var worker = new Worker()
|
var worker = new Worker()
|
||||||
{
|
{
|
||||||
WorkerType = workerType,
|
WorkerType = workerType,
|
||||||
StartTime = DateTime.Now,
|
StartTime = DateTime.UtcNow,
|
||||||
LastRunTime = null,
|
LastRunTime = null,
|
||||||
ExecutionCount = 0,
|
ExecutionCount = 0,
|
||||||
Delay = delay
|
Delay = delay
|
||||||
@@ -47,9 +47,9 @@ public class WorkerService : IWorkerService
|
|||||||
await _workerRepository.EnableWorker(workerType);
|
await _workerRepository.EnableWorker(workerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Worker> GetWorkers()
|
public async Task<IEnumerable<Worker>> GetWorkers()
|
||||||
{
|
{
|
||||||
return _workerRepository.GetWorkers();
|
return await _workerRepository.GetWorkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ToggleWorker(Enums.WorkerType workerType)
|
public async Task<bool> ToggleWorker(Enums.WorkerType workerType)
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ namespace Managing.Application.Abstractions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration</param>
|
/// <param name="config">The trading bot configuration</param>
|
||||||
/// <returns>ITradingBot instance</returns>
|
/// <returns>ITradingBot instance</returns>
|
||||||
ITradingBot CreateTradingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a trading bot for backtesting using the unified TradingBot class
|
/// Creates a trading bot for backtesting using the unified TradingBot class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration</param>
|
/// <param name="config">The trading bot configuration</param>
|
||||||
/// <returns>ITradingBot instance configured for backtesting</returns>
|
/// <returns>ITradingBot instance configured for backtesting</returns>
|
||||||
ITradingBot CreateBacktestTradingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,38 +7,38 @@ namespace Managing.Application.Abstractions;
|
|||||||
|
|
||||||
public interface IBotService
|
public interface IBotService
|
||||||
{
|
{
|
||||||
void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data);
|
Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data);
|
||||||
void AddSimpleBotToCache(IBot bot);
|
void AddSimpleBotToCache(IBot bot);
|
||||||
void AddTradingBotToCache(ITradingBot bot);
|
void AddTradingBotToCache(ITradingBot bot);
|
||||||
List<ITradingBot> GetActiveBots();
|
List<ITradingBot> GetActiveBots();
|
||||||
IEnumerable<BotBackup> GetSavedBots();
|
Task<IEnumerable<BotBackup>> GetSavedBotsAsync();
|
||||||
void StartBotFromBackup(BotBackup backupBot);
|
Task StartBotFromBackup(BotBackup backupBot);
|
||||||
BotBackup GetBotBackup(string identifier);
|
Task<BotBackup> GetBotBackup(string identifier);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a trading bot using the unified TradingBot class
|
/// Creates a trading bot using the unified TradingBot class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration</param>
|
/// <param name="config">The trading bot configuration</param>
|
||||||
/// <returns>ITradingBot instance</returns>
|
/// <returns>ITradingBot instance</returns>
|
||||||
ITradingBot CreateTradingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateTradingBot(TradingBotConfig config);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a trading bot for backtesting using the unified TradingBot class
|
/// Creates a trading bot for backtesting using the unified TradingBot class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The trading bot configuration</param>
|
/// <param name="config">The trading bot configuration</param>
|
||||||
/// <returns>ITradingBot instance configured for backtesting</returns>
|
/// <returns>ITradingBot instance configured for backtesting</returns>
|
||||||
ITradingBot CreateBacktestTradingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config);
|
||||||
|
|
||||||
// Legacy methods - these will use TradingBot internally but maintain backward compatibility
|
// Legacy methods - these will use TradingBot internally but maintain backward compatibility
|
||||||
ITradingBot CreateScalpingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateScalpingBot(TradingBotConfig config);
|
||||||
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config);
|
||||||
ITradingBot CreateFlippingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateFlippingBot(TradingBotConfig config);
|
||||||
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
|
Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config);
|
||||||
|
|
||||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||||
Task<string> StopBot(string botName);
|
Task<string> StopBot(string botName);
|
||||||
Task<bool> DeleteBot(string botName);
|
Task<bool> DeleteBot(string botName);
|
||||||
Task<string> RestartBot(string botName);
|
Task<string> RestartBot(string botName);
|
||||||
void ToggleIsForWatchingOnly(string botName);
|
Task ToggleIsForWatchingOnly(string botName);
|
||||||
Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig);
|
Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig);
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,10 @@ namespace Managing.Application.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IScenarioService
|
public interface IScenarioService
|
||||||
{
|
{
|
||||||
IEnumerable<Scenario> GetScenarios();
|
Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
|
||||||
Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
|
Task<IEnumerable<Indicator>> GetIndicatorsAsync();
|
||||||
IEnumerable<Indicator> GetIndicators();
|
|
||||||
bool DeleteStrategy(string name);
|
|
||||||
bool DeleteScenario(string name);
|
|
||||||
Scenario GetScenario(string name);
|
|
||||||
|
|
||||||
Indicator CreateStrategy(IndicatorType type,
|
Task<Indicator> CreateStrategy(IndicatorType type,
|
||||||
string name,
|
string name,
|
||||||
int? period = null,
|
int? period = null,
|
||||||
int? fastPeriods = null,
|
int? fastPeriods = null,
|
||||||
@@ -25,21 +21,19 @@ namespace Managing.Application.Abstractions
|
|||||||
int? smoothPeriods = null,
|
int? smoothPeriods = null,
|
||||||
int? cyclePeriods = null);
|
int? cyclePeriods = null);
|
||||||
|
|
||||||
bool DeleteStrategies();
|
Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
|
||||||
bool DeleteScenarios();
|
|
||||||
bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod);
|
|
||||||
|
|
||||||
bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods,
|
Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods, int? slowPeriods,
|
||||||
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
|
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods);
|
||||||
|
|
||||||
IEnumerable<Scenario> GetScenariosByUser(User user);
|
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
|
||||||
Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
|
Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1);
|
||||||
IEnumerable<Indicator> GetIndicatorsByUser(User user);
|
Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user);
|
||||||
bool DeleteIndicatorByUser(User user, string name);
|
Task<bool> DeleteIndicatorByUser(User user, string name);
|
||||||
bool DeleteScenarioByUser(User user, string name);
|
Task<bool> DeleteScenarioByUser(User user, string name);
|
||||||
Scenario GetScenarioByUser(User user, string name);
|
Task<Scenario> GetScenarioByUser(User user, string name);
|
||||||
|
|
||||||
Indicator CreateIndicatorForUser(User user,
|
Task<Indicator> CreateIndicatorForUser(User user,
|
||||||
IndicatorType type,
|
IndicatorType type,
|
||||||
string name,
|
string name,
|
||||||
int? period = null,
|
int? period = null,
|
||||||
@@ -51,11 +45,11 @@ namespace Managing.Application.Abstractions
|
|||||||
int? smoothPeriods = null,
|
int? smoothPeriods = null,
|
||||||
int? cyclePeriods = null);
|
int? cyclePeriods = null);
|
||||||
|
|
||||||
bool DeleteStrategiesByUser(User user);
|
Task<bool> DeleteStrategiesByUser(User user);
|
||||||
bool DeleteScenariosByUser(User user);
|
Task<bool> DeleteScenariosByUser(User user);
|
||||||
bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
|
Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod);
|
||||||
|
|
||||||
bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
||||||
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods,
|
int? slowPeriods, int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods,
|
||||||
int? cyclePeriods);
|
int? cyclePeriods);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
namespace Managing.Application.Abstractions;
|
using Managing.Domain.Users;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions;
|
||||||
|
|
||||||
public interface ISettingsService
|
public interface ISettingsService
|
||||||
{
|
{
|
||||||
bool SetupSettings();
|
Task<bool> SetupSettings();
|
||||||
Task<bool> ResetSettings();
|
Task<bool> ResetSettings();
|
||||||
Task<bool> CreateDefaultConfiguration(Domain.Users.User user);
|
Task<bool> CreateDefaultConfiguration(User user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Managing.Domain.Accounts;
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Strategies.Base;
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -16,7 +15,7 @@ namespace Managing.Application.Abstractions
|
|||||||
Account Account { get; set; }
|
Account Account { get; set; }
|
||||||
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||||
HashSet<Candle> Candles { get; set; }
|
HashSet<Candle> Candles { get; set; }
|
||||||
HashSet<Signal> Signals { get; set; }
|
HashSet<LightSignal> Signals { get; set; }
|
||||||
List<Position> Positions { get; set; }
|
List<Position> Positions { get; set; }
|
||||||
Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
|
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
|
||||||
@@ -24,7 +23,7 @@ namespace Managing.Application.Abstractions
|
|||||||
DateTime CreateDate { get; }
|
DateTime CreateDate { get; }
|
||||||
DateTime PreloadSince { get; set; }
|
DateTime PreloadSince { get; set; }
|
||||||
int PreloadedCandlesCount { get; set; }
|
int PreloadedCandlesCount { get; set; }
|
||||||
decimal Fee { get; set; }
|
|
||||||
|
|
||||||
Task Run();
|
Task Run();
|
||||||
Task ToggleIsForWatchOnly();
|
Task ToggleIsForWatchOnly();
|
||||||
@@ -36,7 +35,7 @@ namespace Managing.Application.Abstractions
|
|||||||
Task LoadAccount();
|
Task LoadAccount();
|
||||||
Task<Position> OpenPositionManually(TradeDirection direction);
|
Task<Position> OpenPositionManually(TradeDirection direction);
|
||||||
|
|
||||||
Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||||
bool tradeClosingPosition = false);
|
bool tradeClosingPosition = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -100,9 +100,18 @@ public class AccountService : IAccountService
|
|||||||
public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
|
public async Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
|
||||||
{
|
{
|
||||||
var account = await _accountRepository.GetAccountByNameAsync(name);
|
var account = await _accountRepository.GetAccountByNameAsync(name);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Account '{name}' not found");
|
||||||
|
}
|
||||||
|
|
||||||
ManageProperties(hideSecrets, getBalance, account);
|
ManageProperties(hideSecrets, getBalance, account);
|
||||||
|
|
||||||
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
|
if (account.User == null && account.User != null)
|
||||||
|
{
|
||||||
|
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
|
||||||
|
}
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
@@ -123,9 +132,31 @@ public class AccountService : IAccountService
|
|||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance)
|
public async Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true,
|
||||||
|
bool getBalance = false)
|
||||||
{
|
{
|
||||||
var result = _accountRepository.GetAccounts();
|
var account = await _accountRepository.GetAccountByNameAsync(accountName);
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
ManageProperties(hideSecrets, getBalance, account);
|
||||||
|
if (account.User != null)
|
||||||
|
{
|
||||||
|
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
|
||||||
|
{
|
||||||
|
return await GetAccountsAsync(hideSecrets, getBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance)
|
||||||
|
{
|
||||||
|
var result = await _accountRepository.GetAccountsAsync();
|
||||||
var accounts = new List<Account>();
|
var accounts = new List<Account>();
|
||||||
|
|
||||||
foreach (var account in result)
|
foreach (var account in result)
|
||||||
@@ -139,15 +170,21 @@ public class AccountService : IAccountService
|
|||||||
|
|
||||||
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
|
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
|
||||||
{
|
{
|
||||||
var cacheKey = $"user-account-{user.Name}";
|
return GetAccountsByUserAsync(user, hideSecrets).Result;
|
||||||
|
|
||||||
return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
|
|
||||||
TimeSpan.FromMinutes(5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Account> GetAccounts(User user, bool hideSecrets, bool getBalance)
|
public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true)
|
||||||
{
|
{
|
||||||
var result = _accountRepository.GetAccounts();
|
var cacheKey = $"user-account-{user.Name}";
|
||||||
|
|
||||||
|
// For now, we'll get fresh data since caching async operations requires more complex logic
|
||||||
|
// This can be optimized later with proper async caching
|
||||||
|
return await GetAccountsAsync(user, hideSecrets, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
|
||||||
|
{
|
||||||
|
var result = await _accountRepository.GetAccountsAsync();
|
||||||
var accounts = new List<Account>();
|
var accounts = new List<Account>();
|
||||||
|
|
||||||
foreach (var account in result.Where(a => a.User.Name == user.Name))
|
foreach (var account in result.Where(a => a.User.Name == user.Name))
|
||||||
@@ -161,11 +198,16 @@ public class AccountService : IAccountService
|
|||||||
|
|
||||||
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
|
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
|
||||||
{
|
{
|
||||||
var cacheKey = $"user-account-balance-{user.Name}";
|
return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
|
||||||
var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); },
|
}
|
||||||
TimeSpan.FromHours(3));
|
|
||||||
|
|
||||||
return accounts;
|
public async Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets)
|
||||||
|
{
|
||||||
|
var cacheKey = $"user-account-balance-{user.Name}";
|
||||||
|
|
||||||
|
// For now, get fresh data since caching async operations requires more complex logic
|
||||||
|
// This can be optimized later with proper async caching
|
||||||
|
return await GetAccountsAsync(user, hideSecrets, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName)
|
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName)
|
||||||
@@ -200,7 +242,8 @@ public class AccountService : IAccountService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
|
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
|
||||||
|
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
|
||||||
{
|
{
|
||||||
// Get the account for the user
|
// Get the account for the user
|
||||||
var account = await GetAccountByUser(user, accountName, true, false);
|
var account = await GetAccountByUser(user, accountName, true, false);
|
||||||
@@ -239,7 +282,8 @@ public class AccountService : IAccountService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null)
|
public async Task<SwapInfos> SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker,
|
||||||
|
decimal amount, int? chainId = null)
|
||||||
{
|
{
|
||||||
// Get the account for the user
|
// Get the account for the user
|
||||||
var account = await GetAccountByUser(user, accountName, true, false);
|
var account = await GetAccountByUser(user, accountName, true, false);
|
||||||
|
|||||||
@@ -121,6 +121,12 @@ namespace Managing.Application.Backtesting
|
|||||||
result.StartDate = startDate;
|
result.StartDate = startDate;
|
||||||
result.EndDate = endDate;
|
result.EndDate = endDate;
|
||||||
|
|
||||||
|
// Ensure RequestId is set - required for PostgreSQL NOT NULL constraint
|
||||||
|
if (string.IsNullOrEmpty(result.RequestId))
|
||||||
|
{
|
||||||
|
result.RequestId = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
if (save && user != null)
|
if (save && user != null)
|
||||||
{
|
{
|
||||||
_backtestRepository.InsertBacktestForUser(user, result);
|
_backtestRepository.InsertBacktestForUser(user, result);
|
||||||
@@ -138,8 +144,9 @@ namespace Managing.Application.Backtesting
|
|||||||
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
|
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user);
|
||||||
if (refundSuccess)
|
if (refundSuccess)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogError(
|
||||||
"Successfully refunded credits for user {UserName} after backtest failure", user.Name);
|
"Successfully refunded credits for user {UserName} after backtest failure: {message}",
|
||||||
|
user.Name, ex.Message);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -188,7 +195,7 @@ namespace Managing.Application.Backtesting
|
|||||||
string requestId = null,
|
string requestId = null,
|
||||||
object metadata = null)
|
object metadata = null)
|
||||||
{
|
{
|
||||||
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
|
var tradingBot = await _botFactory.CreateBacktestTradingBot(config);
|
||||||
|
|
||||||
// Scenario and indicators should already be loaded in constructor by BotService
|
// Scenario and indicators should already be loaded in constructor by BotService
|
||||||
// This is just a validation check to ensure everything loaded properly
|
// This is just a validation check to ensure everything loaded properly
|
||||||
@@ -215,26 +222,7 @@ namespace Managing.Application.Backtesting
|
|||||||
|
|
||||||
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
|
private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
var accounts = _accountService.GetAccounts(false, false).ToArray();
|
return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
|
||||||
var account = accounts.FirstOrDefault(a =>
|
|
||||||
a.Name.Equals(config.AccountName, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
a.Exchange == TradingExchanges.GmxV2);
|
|
||||||
|
|
||||||
if (account == null && accounts.Any())
|
|
||||||
{
|
|
||||||
account = accounts.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account != null)
|
|
||||||
{
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Account
|
|
||||||
{
|
|
||||||
Name = config.AccountName,
|
|
||||||
Exchange = TradingExchanges.GmxV2
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
|
private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
|
||||||
@@ -270,13 +258,13 @@ namespace Managing.Application.Backtesting
|
|||||||
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
||||||
totalCandles, config.Ticker, config.Timeframe);
|
totalCandles, config.Ticker, config.Timeframe);
|
||||||
|
|
||||||
bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance);
|
bot.WalletBalances.Add(candles.FirstOrDefault()!.Date, config.BotTradingBalance);
|
||||||
|
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
bot.OptimizedCandles.Enqueue(candle);
|
bot.OptimizedCandles.Enqueue(candle);
|
||||||
bot.Candles.Add(candle);
|
bot.Candles.Add(candle);
|
||||||
bot.Run();
|
await bot.Run();
|
||||||
|
|
||||||
currentCandle++;
|
currentCandle++;
|
||||||
|
|
||||||
@@ -318,8 +306,6 @@ namespace Managing.Application.Backtesting
|
|||||||
|
|
||||||
var finalPnl = bot.GetProfitAndLoss();
|
var finalPnl = bot.GetProfitAndLoss();
|
||||||
var winRate = bot.GetWinRate();
|
var winRate = bot.GetWinRate();
|
||||||
var optimizedMoneyManagement =
|
|
||||||
TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement);
|
|
||||||
var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
|
var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
|
||||||
var growthPercentage =
|
var growthPercentage =
|
||||||
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
|
TradingHelpers.GetGrowthFromInitalBalance(bot.WalletBalances.FirstOrDefault().Value, finalPnl);
|
||||||
@@ -357,7 +343,6 @@ namespace Managing.Application.Backtesting
|
|||||||
Fees = fees,
|
Fees = fees,
|
||||||
WalletBalances = bot.WalletBalances.ToList(),
|
WalletBalances = bot.WalletBalances.ToList(),
|
||||||
Statistics = stats,
|
Statistics = stats,
|
||||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
|
||||||
IndicatorsValues = withCandles
|
IndicatorsValues = withCandles
|
||||||
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
|
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
|
||||||
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
||||||
@@ -442,11 +427,11 @@ namespace Managing.Application.Backtesting
|
|||||||
return indicatorsValues;
|
return indicatorsValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteBacktest(string id)
|
public async Task<bool> DeleteBacktestAsync(string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBacktestByIdForUser(null, id);
|
await _backtestRepository.DeleteBacktestByIdForUserAsync(null, id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -476,12 +461,24 @@ namespace Managing.Application.Backtesting
|
|||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
|
||||||
|
{
|
||||||
|
var backtests = await _backtestRepository.GetBacktestsByUserAsync(user);
|
||||||
|
return backtests;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
||||||
{
|
{
|
||||||
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
|
||||||
|
{
|
||||||
|
var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
|
||||||
|
return backtests;
|
||||||
|
}
|
||||||
|
|
||||||
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
|
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
|
||||||
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
@@ -490,9 +487,19 @@ namespace Managing.Application.Backtesting
|
|||||||
return (backtests, totalCount);
|
return (backtests, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Backtest GetBacktestByIdForUser(User user, string id)
|
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
|
||||||
|
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
|
var (backtests, totalCount) =
|
||||||
|
await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
|
||||||
|
sortOrder);
|
||||||
|
return (backtests, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
|
||||||
|
{
|
||||||
|
var backtest = await _backtestRepository.GetBacktestByIdForUserAsync(user, id);
|
||||||
|
|
||||||
if (backtest == null)
|
if (backtest == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -504,12 +511,12 @@ namespace Managing.Application.Backtesting
|
|||||||
var account = new Account
|
var account = new Account
|
||||||
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
|
{ Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };
|
||||||
|
|
||||||
var candles = _exchangeService.GetCandlesInflux(
|
var candles = await _exchangeService.GetCandlesInflux(
|
||||||
account.Exchange,
|
account.Exchange,
|
||||||
backtest.Config.Ticker,
|
backtest.Config.Ticker,
|
||||||
backtest.StartDate,
|
backtest.StartDate,
|
||||||
backtest.Config.Timeframe,
|
backtest.Config.Timeframe,
|
||||||
backtest.EndDate).Result;
|
backtest.EndDate);
|
||||||
|
|
||||||
if (candles != null && candles.Count > 0)
|
if (candles != null && candles.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -525,11 +532,11 @@ namespace Managing.Application.Backtesting
|
|||||||
return backtest;
|
return backtest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteBacktestByUser(User user, string id)
|
public async Task<bool> DeleteBacktestByUserAsync(User user, string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBacktestByIdForUser(user, id);
|
await _backtestRepository.DeleteBacktestByIdForUserAsync(user, id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -539,11 +546,11 @@ namespace Managing.Application.Backtesting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids)
|
public async Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBacktestsByIdsForUser(user, ids);
|
await _backtestRepository.DeleteBacktestsByIdsForUserAsync(user, ids);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -567,11 +574,11 @@ namespace Managing.Application.Backtesting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteBacktestsByRequestId(string requestId)
|
public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBacktestsByRequestId(requestId);
|
await _backtestRepository.DeleteBacktestsByRequestIdAsync(requestId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -589,6 +596,14 @@ namespace Managing.Application.Backtesting
|
|||||||
return (backtests, totalCount);
|
return (backtests, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(
|
||||||
|
User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
|
{
|
||||||
|
var (backtests, totalCount) =
|
||||||
|
await _backtestRepository.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
|
||||||
|
return (backtests, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
// Bundle backtest methods
|
// Bundle backtest methods
|
||||||
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
|
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
|
||||||
{
|
{
|
||||||
@@ -600,27 +615,53 @@ namespace Managing.Application.Backtesting
|
|||||||
return _backtestRepository.GetBundleBacktestRequestsByUser(user);
|
return _backtestRepository.GetBundleBacktestRequestsByUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
|
||||||
|
{
|
||||||
|
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
|
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
|
||||||
{
|
{
|
||||||
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
|
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
|
||||||
|
{
|
||||||
|
return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
|
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
|
||||||
{
|
{
|
||||||
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
|
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
|
||||||
|
{
|
||||||
|
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
|
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
|
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
|
||||||
|
{
|
||||||
|
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
|
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
|
||||||
{
|
{
|
||||||
// Use the repository method to get all bundles, then filter by status
|
// Use the repository method to get all bundles, then filter by status
|
||||||
return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
|
return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(
|
||||||
|
BundleBacktestRequestStatus status)
|
||||||
|
{
|
||||||
|
return await _backtestRepository.GetBundleBacktestRequestsByStatusAsync(status);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
|
/// Sends a LightBacktestResponse to all SignalR subscribers of a bundle request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.ManageBot;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -14,6 +15,7 @@ namespace Managing.Application.Bots.Base
|
|||||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
private readonly ILogger<TradingBot> _tradingBotLogger;
|
||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
private readonly IBotService _botService;
|
private readonly IBotService _botService;
|
||||||
|
private readonly IBackupBotService _backupBotService;
|
||||||
|
|
||||||
public BotFactory(
|
public BotFactory(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
@@ -21,7 +23,8 @@ namespace Managing.Application.Bots.Base
|
|||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ITradingService tradingService,
|
ITradingService tradingService,
|
||||||
IBotService botService)
|
IBotService botService,
|
||||||
|
IBackupBotService backupBotService)
|
||||||
{
|
{
|
||||||
_tradingBotLogger = tradingBotLogger;
|
_tradingBotLogger = tradingBotLogger;
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
@@ -29,23 +32,24 @@ namespace Managing.Application.Bots.Base
|
|||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
_botService = botService;
|
_botService = botService;
|
||||||
|
_backupBotService = backupBotService;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
|
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
|
||||||
{
|
{
|
||||||
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
|
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService, _backupBotService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ITradingBot IBotFactory.CreateTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Delegate to BotService which handles scenario loading properly
|
// Delegate to BotService which handles scenario loading properly
|
||||||
return _botService.CreateTradingBot(config);
|
return await _botService.CreateTradingBot(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
ITradingBot IBotFactory.CreateBacktestTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Delegate to BotService which handles scenario loading properly
|
// Delegate to BotService which handles scenario loading properly
|
||||||
return _botService.CreateBacktestTradingBot(config);
|
return await _botService.CreateBacktestTradingBot(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
|
using Managing.Application.ManageBot;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -10,13 +11,16 @@ namespace Managing.Application.Bots
|
|||||||
{
|
{
|
||||||
public readonly ILogger<TradingBot> Logger;
|
public readonly ILogger<TradingBot> Logger;
|
||||||
private readonly IBotService _botService;
|
private readonly IBotService _botService;
|
||||||
|
private readonly IBackupBotService _backupBotService;
|
||||||
private Workflow _workflow;
|
private Workflow _workflow;
|
||||||
|
|
||||||
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService) :
|
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService,
|
||||||
|
IBackupBotService backupBotService) :
|
||||||
base(name)
|
base(name)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_botService = botService;
|
_botService = botService;
|
||||||
|
_backupBotService = backupBotService;
|
||||||
_workflow = workflow;
|
_workflow = workflow;
|
||||||
Interval = 100;
|
Interval = 100;
|
||||||
}
|
}
|
||||||
@@ -35,20 +39,20 @@ namespace Managing.Application.Bots
|
|||||||
Logger.LogInformation(Identifier);
|
Logger.LogInformation(Identifier);
|
||||||
Logger.LogInformation(DateTime.Now.ToString());
|
Logger.LogInformation(DateTime.Now.ToString());
|
||||||
await _workflow.Execute();
|
await _workflow.Execute();
|
||||||
SaveBackup();
|
await SaveBackup();
|
||||||
Logger.LogInformation("__________________________________________________");
|
Logger.LogInformation("__________________________________________________");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SaveBackup()
|
public override async Task SaveBackup()
|
||||||
{
|
{
|
||||||
var data = JsonConvert.SerializeObject(_workflow);
|
var data = JsonConvert.SerializeObject(_workflow);
|
||||||
_botService.SaveOrUpdateBotBackup(User, Identifier, Status, data);
|
await _backupBotService.SaveOrUpdateBotBackup(User, Identifier, Status, new TradingBotBackup());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LoadBackup(BotBackup backup)
|
public override void LoadBackup(BotBackup backup)
|
||||||
{
|
{
|
||||||
_workflow = JsonConvert.DeserializeObject<Workflow>(backup.Data);
|
_workflow = new Workflow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,14 @@ using System.Text.Json;
|
|||||||
using GeneticSharp;
|
using GeneticSharp;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Risk;
|
using Managing.Domain.Risk;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ public class GeneticService : IGeneticService
|
|||||||
private readonly IBacktester _backtester;
|
private readonly IBacktester _backtester;
|
||||||
private readonly ILogger<GeneticService> _logger;
|
private readonly ILogger<GeneticService> _logger;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
|
||||||
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
|
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
|
||||||
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
|
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
|
||||||
@@ -188,12 +191,14 @@ public class GeneticService : IGeneticService
|
|||||||
IGeneticRepository geneticRepository,
|
IGeneticRepository geneticRepository,
|
||||||
IBacktester backtester,
|
IBacktester backtester,
|
||||||
ILogger<GeneticService> logger,
|
ILogger<GeneticService> logger,
|
||||||
IMessengerService messengerService)
|
IMessengerService messengerService,
|
||||||
|
IServiceScopeFactory serviceScopeFactory)
|
||||||
{
|
{
|
||||||
_geneticRepository = geneticRepository;
|
_geneticRepository = geneticRepository;
|
||||||
_backtester = backtester;
|
_backtester = backtester;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeneticRequest CreateGeneticRequest(
|
public GeneticRequest CreateGeneticRequest(
|
||||||
@@ -247,9 +252,9 @@ public class GeneticService : IGeneticService
|
|||||||
return _geneticRepository.GetGeneticRequestByIdForUser(user, id);
|
return _geneticRepository.GetGeneticRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
|
public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest)
|
||||||
{
|
{
|
||||||
_geneticRepository.UpdateGeneticRequest(geneticRequest);
|
await _geneticRepository.UpdateGeneticRequestAsync(geneticRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteGeneticRequestByIdForUser(User user, string id)
|
public void DeleteGeneticRequestByIdForUser(User user, string id)
|
||||||
@@ -257,9 +262,9 @@ public class GeneticService : IGeneticService
|
|||||||
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
|
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
|
public Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status)
|
||||||
{
|
{
|
||||||
return _geneticRepository.GetPendingGeneticRequests();
|
return _geneticRepository.GetGeneticRequestsAsync(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -277,7 +282,7 @@ public class GeneticService : IGeneticService
|
|||||||
|
|
||||||
// Update status to running
|
// Update status to running
|
||||||
request.Status = GeneticRequestStatus.Running;
|
request.Status = GeneticRequestStatus.Running;
|
||||||
UpdateGeneticRequest(request);
|
await UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
// Create or resume chromosome for trading bot configuration
|
// Create or resume chromosome for trading bot configuration
|
||||||
TradingBotChromosome chromosome;
|
TradingBotChromosome chromosome;
|
||||||
@@ -307,7 +312,7 @@ public class GeneticService : IGeneticService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create fitness function first
|
// Create fitness function first
|
||||||
var fitness = new TradingBotFitness(_backtester, request, _logger);
|
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
|
||||||
|
|
||||||
// Create genetic algorithm with better configuration
|
// Create genetic algorithm with better configuration
|
||||||
var ga = new GeneticAlgorithm(
|
var ga = new GeneticAlgorithm(
|
||||||
@@ -341,7 +346,7 @@ public class GeneticService : IGeneticService
|
|||||||
|
|
||||||
// Run the genetic algorithm with periodic checks for cancellation
|
// Run the genetic algorithm with periodic checks for cancellation
|
||||||
var generationCount = 0;
|
var generationCount = 0;
|
||||||
ga.GenerationRan += (sender, e) =>
|
ga.GenerationRan += async (sender, e) =>
|
||||||
{
|
{
|
||||||
generationCount = ga.GenerationsNumber;
|
generationCount = ga.GenerationsNumber;
|
||||||
|
|
||||||
@@ -362,7 +367,7 @@ public class GeneticService : IGeneticService
|
|||||||
request.BestChromosome = JsonSerializer.Serialize(geneValues);
|
request.BestChromosome = JsonSerializer.Serialize(geneValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateGeneticRequest(request);
|
await UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
// Check for cancellation
|
// Check for cancellation
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
@@ -381,7 +386,7 @@ public class GeneticService : IGeneticService
|
|||||||
|
|
||||||
// Update request status to pending so it can be resumed
|
// Update request status to pending so it can be resumed
|
||||||
request.Status = GeneticRequestStatus.Pending;
|
request.Status = GeneticRequestStatus.Pending;
|
||||||
UpdateGeneticRequest(request);
|
await UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
return new GeneticAlgorithmResult
|
return new GeneticAlgorithmResult
|
||||||
{
|
{
|
||||||
@@ -413,7 +418,7 @@ public class GeneticService : IGeneticService
|
|||||||
completed_at = DateTime.UtcNow
|
completed_at = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
UpdateGeneticRequest(request);
|
await UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
// Send notification about the completed genetic algorithm
|
// Send notification about the completed genetic algorithm
|
||||||
try
|
try
|
||||||
@@ -442,7 +447,7 @@ public class GeneticService : IGeneticService
|
|||||||
request.Status = GeneticRequestStatus.Failed;
|
request.Status = GeneticRequestStatus.Failed;
|
||||||
request.ErrorMessage = ex.Message;
|
request.ErrorMessage = ex.Message;
|
||||||
request.CompletedAt = DateTime.UtcNow;
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
UpdateGeneticRequest(request);
|
await UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -505,7 +510,7 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
private readonly List<IndicatorType> _eligibleIndicators;
|
private readonly List<IndicatorType> _eligibleIndicators;
|
||||||
private readonly double _maxTakeProfit;
|
private readonly double _maxTakeProfit;
|
||||||
private readonly Random _random = new Random();
|
private readonly Random _random = new Random();
|
||||||
private int[]? _indicatorSelectionPattern;
|
private int[] _indicatorSelectionPattern;
|
||||||
|
|
||||||
// Gene structure:
|
// Gene structure:
|
||||||
// 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
|
// 0-3: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
|
||||||
@@ -552,7 +557,7 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
GenerateIndicatorSelectionPattern();
|
GenerateIndicatorSelectionPattern();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Gene(_indicatorSelectionPattern![geneIndex - 5]);
|
return new Gene(_indicatorSelectionPattern[geneIndex - 5]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -790,7 +795,7 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
return _random.Next((int)range.min, (int)range.max + 1);
|
return _random.Next((int)range.min, (int)range.max + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetParameterName(int index)
|
private string GetParameterName(int index)
|
||||||
{
|
{
|
||||||
return index switch
|
return index switch
|
||||||
{
|
{
|
||||||
@@ -879,14 +884,14 @@ public class GeneticIndicator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TradingBotFitness : IFitness
|
public class TradingBotFitness : IFitness
|
||||||
{
|
{
|
||||||
private readonly IBacktester _backtester;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly GeneticRequest _request;
|
private readonly GeneticRequest _request;
|
||||||
private GeneticAlgorithm _geneticAlgorithm;
|
private GeneticAlgorithm _geneticAlgorithm;
|
||||||
private readonly ILogger<GeneticService> _logger;
|
private readonly ILogger<GeneticService> _logger;
|
||||||
|
|
||||||
public TradingBotFitness(IBacktester backtester, GeneticRequest request, ILogger<GeneticService> logger)
|
public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request, ILogger<GeneticService> logger)
|
||||||
{
|
{
|
||||||
_backtester = backtester;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_request = request;
|
_request = request;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -909,19 +914,22 @@ public class TradingBotFitness : IFitness
|
|||||||
// Get current generation number (default to 0 if not available)
|
// Get current generation number (default to 0 if not available)
|
||||||
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
|
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
|
||||||
|
|
||||||
// Run backtest
|
// Run backtest using scoped service to avoid DbContext concurrency issues
|
||||||
var backtest = _backtester.RunTradingBotBacktest(
|
var backtest = ServiceScopeHelpers.WithScopedService<IBacktester, Backtest>(
|
||||||
config,
|
_serviceScopeFactory,
|
||||||
_request.StartDate,
|
backtester => backtester.RunTradingBotBacktest(
|
||||||
_request.EndDate,
|
config,
|
||||||
_request.User,
|
_request.StartDate,
|
||||||
true,
|
_request.EndDate,
|
||||||
false, // Don't include candles
|
_request.User,
|
||||||
_request.RequestId,
|
true,
|
||||||
new
|
false, // Don't include candles
|
||||||
{
|
_request.RequestId,
|
||||||
generation = currentGeneration
|
new
|
||||||
}
|
{
|
||||||
|
generation = currentGeneration
|
||||||
|
}
|
||||||
|
)
|
||||||
).Result;
|
).Result;
|
||||||
|
|
||||||
// Calculate multi-objective fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
@@ -929,8 +937,9 @@ public class TradingBotFitness : IFitness
|
|||||||
|
|
||||||
return fitness;
|
return fitness;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
|
||||||
// Return low fitness for failed backtests
|
// Return low fitness for failed backtests
|
||||||
return 0.1;
|
return 0.1;
|
||||||
}
|
}
|
||||||
|
|||||||
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.Bots;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot
|
namespace Managing.Application.ManageBot
|
||||||
@@ -22,13 +22,16 @@ namespace Managing.Application.ManageBot
|
|||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IBackupBotService _backupBotService;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
||||||
new ConcurrentDictionary<string, BotTaskWrapper>();
|
new ConcurrentDictionary<string, BotTaskWrapper>();
|
||||||
|
|
||||||
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
||||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
|
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
|
||||||
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
|
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
|
||||||
|
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory)
|
||||||
{
|
{
|
||||||
_botRepository = botRepository;
|
_botRepository = botRepository;
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
@@ -38,35 +41,8 @@ namespace Managing.Application.ManageBot
|
|||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
}
|
_backupBotService = backupBotService;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
public BotBackup GetBotBackup(string identifier)
|
|
||||||
{
|
|
||||||
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data)
|
|
||||||
{
|
|
||||||
var backup = GetBotBackup(identifier);
|
|
||||||
|
|
||||||
if (backup != null)
|
|
||||||
{
|
|
||||||
backup.LastStatus = status;
|
|
||||||
backup.Data = data;
|
|
||||||
_botRepository.UpdateBackupBot(backup);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var botBackup = new BotBackup
|
|
||||||
{
|
|
||||||
LastStatus = status,
|
|
||||||
User = user,
|
|
||||||
Identifier = identifier,
|
|
||||||
Data = data
|
|
||||||
};
|
|
||||||
|
|
||||||
_botRepository.InsertBotAsync(botBackup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BotTaskWrapper
|
public class BotTaskWrapper
|
||||||
@@ -96,6 +72,27 @@ namespace Managing.Application.ManageBot
|
|||||||
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task InitBot(ITradingBot bot, BotBackup backupBot)
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUser(backupBot.User.Name);
|
||||||
|
bot.User = user;
|
||||||
|
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
|
||||||
|
bot.LoadBackup(backupBot);
|
||||||
|
|
||||||
|
// Only start the bot if the backup status is Up
|
||||||
|
if (backupBot.LastStatus == BotStatus.Up)
|
||||||
|
{
|
||||||
|
// Start the bot asynchronously without waiting for completion
|
||||||
|
_ = Task.Run(() => bot.Start());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Keep the bot in Down status if it was originally Down
|
||||||
|
bot.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<ITradingBot> GetActiveBots()
|
public List<ITradingBot> GetActiveBots()
|
||||||
{
|
{
|
||||||
var bots = _botTasks.Values
|
var bots = _botTasks.Values
|
||||||
@@ -107,17 +104,17 @@ namespace Managing.Application.ManageBot
|
|||||||
return bots;
|
return bots;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BotBackup> GetSavedBots()
|
public async Task<IEnumerable<BotBackup>> GetSavedBotsAsync()
|
||||||
{
|
{
|
||||||
return _botRepository.GetBots();
|
return await _botRepository.GetBotsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartBotFromBackup(BotBackup backupBot)
|
public async Task StartBotFromBackup(BotBackup backupBot)
|
||||||
{
|
{
|
||||||
object bot = null;
|
object bot = null;
|
||||||
Task botTask = null;
|
Task botTask = null;
|
||||||
|
|
||||||
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
var scalpingBotData = backupBot.Data;
|
||||||
|
|
||||||
// Get the config directly from the backup
|
// Get the config directly from the backup
|
||||||
var scalpingConfig = scalpingBotData.Config;
|
var scalpingConfig = scalpingBotData.Config;
|
||||||
@@ -137,7 +134,7 @@ namespace Managing.Application.ManageBot
|
|||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
|
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
scalpingConfig.Scenario = scenario;
|
scalpingConfig.Scenario = scenario;
|
||||||
@@ -158,7 +155,7 @@ namespace Managing.Application.ManageBot
|
|||||||
// Ensure critical properties are set correctly for restored bots
|
// Ensure critical properties are set correctly for restored bots
|
||||||
scalpingConfig.IsForBacktest = false;
|
scalpingConfig.IsForBacktest = false;
|
||||||
|
|
||||||
bot = CreateTradingBot(scalpingConfig);
|
bot = await CreateTradingBot(scalpingConfig);
|
||||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||||
|
|
||||||
if (bot != null && botTask != null)
|
if (bot != null && botTask != null)
|
||||||
@@ -168,29 +165,38 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitBot(ITradingBot bot, BotBackup backupBot)
|
public async Task<BotBackup> GetBotBackup(string identifier)
|
||||||
{
|
{
|
||||||
var user = _userService.GetUser(backupBot.User.Name);
|
return await _botRepository.GetBotByIdentifierAsync(identifier);
|
||||||
bot.User = user;
|
}
|
||||||
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
|
|
||||||
bot.LoadBackup(backupBot);
|
|
||||||
|
|
||||||
// Only start the bot if the backup status is Up
|
public async Task SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, TradingBotBackup data)
|
||||||
if (backupBot.LastStatus == BotStatus.Up)
|
{
|
||||||
|
var backup = await GetBotBackup(identifier);
|
||||||
|
|
||||||
|
if (backup != null)
|
||||||
{
|
{
|
||||||
// Start the bot asynchronously without waiting for completion
|
backup.LastStatus = status;
|
||||||
_ = Task.Run(() => bot.Start());
|
backup.Data = data;
|
||||||
|
await _botRepository.UpdateBackupBot(backup);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Keep the bot in Down status if it was originally Down
|
var botBackup = new BotBackup
|
||||||
bot.Stop();
|
{
|
||||||
|
LastStatus = status,
|
||||||
|
User = user,
|
||||||
|
Identifier = identifier,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
|
||||||
|
await _botRepository.InsertBotAsync(botBackup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBot CreateSimpleBot(string botName, Workflow workflow)
|
public IBot CreateSimpleBot(string botName, Workflow workflow)
|
||||||
{
|
{
|
||||||
return new SimpleBot(botName, _tradingBotLogger, workflow, this);
|
return new SimpleBot(botName, _tradingBotLogger, workflow, this, _backupBotService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> StopBot(string identifier)
|
public async Task<string> StopBot(string identifier)
|
||||||
@@ -282,12 +288,12 @@ namespace Managing.Application.ManageBot
|
|||||||
return BotStatus.Down.ToString();
|
return BotStatus.Down.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleIsForWatchingOnly(string identifier)
|
public async Task ToggleIsForWatchingOnly(string identifier)
|
||||||
{
|
{
|
||||||
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
||||||
botTaskWrapper.BotInstance is ITradingBot tradingBot)
|
botTaskWrapper.BotInstance is ITradingBot tradingBot)
|
||||||
{
|
{
|
||||||
tradingBot.ToggleIsForWatchOnly().Wait();
|
await tradingBot.ToggleIsForWatchOnly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +311,7 @@ namespace Managing.Application.ManageBot
|
|||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
newConfig.Scenario = scenario;
|
newConfig.Scenario = scenario;
|
||||||
@@ -365,12 +371,12 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ITradingBot CreateTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -386,22 +392,15 @@ namespace Managing.Application.ManageBot
|
|||||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateBacktestTradingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -418,22 +417,15 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.IsForBacktest = true;
|
config.IsForBacktest = true;
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateScalpingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -450,22 +442,15 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.FlipPosition = false;
|
config.FlipPosition = false;
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -483,22 +468,15 @@ namespace Managing.Application.ManageBot
|
|||||||
|
|
||||||
config.IsForBacktest = true;
|
config.IsForBacktest = true;
|
||||||
config.FlipPosition = false;
|
config.FlipPosition = false;
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateFlippingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -515,22 +493,15 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.FlipPosition = true;
|
config.FlipPosition = true;
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
|
public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
|
||||||
{
|
{
|
||||||
// Ensure the scenario is properly loaded from database if needed
|
// Ensure the scenario is properly loaded from database if needed
|
||||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||||
if (scenario != null)
|
if (scenario != null)
|
||||||
{
|
{
|
||||||
config.Scenario = scenario;
|
config.Scenario = scenario;
|
||||||
@@ -548,14 +519,7 @@ namespace Managing.Application.ManageBot
|
|||||||
|
|
||||||
config.IsForBacktest = true;
|
config.IsForBacktest = true;
|
||||||
config.FlipPosition = true;
|
config.FlipPosition = true;
|
||||||
return new TradingBot(
|
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||||
_exchangeService,
|
|
||||||
_tradingBotLogger,
|
|
||||||
_tradingService,
|
|
||||||
_accountService,
|
|
||||||
_messengerService,
|
|
||||||
this,
|
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ namespace Managing.Application.ManageBot
|
|||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request,
|
public Task<Dictionary<User, List<ITradingBot>>> Handle(GetAllAgentsCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<User, List<ITradingBot>>();
|
var result = new Dictionary<User, List<ITradingBot>>();
|
||||||
@@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot
|
|||||||
result[bot.User].Add(bot);
|
result[bot.User].Add(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
_botService = botService;
|
_botService = botService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
|
public async Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var backupBots = _botService.GetSavedBots().ToList();
|
var backupBots = (await _botService.GetSavedBotsAsync()).ToList();
|
||||||
_logger.LogInformation("Loading {Count} backup bots.", backupBots.Count);
|
_logger.LogInformation("Loading {Count} backup bots.", backupBots.Count);
|
||||||
|
|
||||||
var result = new Dictionary<string, BotStatus>();
|
var result = new Dictionary<string, BotStatus>();
|
||||||
@@ -42,7 +42,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
_botService.StartBotFromBackup(backupBot);
|
_botService.StartBotFromBackup(backupBot);
|
||||||
|
|
||||||
// Wait a short time to allow the bot to initialize
|
// Wait a short time to allow the bot to initialize
|
||||||
Thread.Sleep(1000);
|
await Task.Delay(1000, cancellationToken);
|
||||||
|
|
||||||
// Try to get the active bot multiple times to ensure it's properly started
|
// Try to get the active bot multiple times to ensure it's properly started
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
@@ -74,7 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
attempts++;
|
attempts++;
|
||||||
if (attempts < maxAttempts)
|
if (attempts < maxAttempts)
|
||||||
{
|
{
|
||||||
Thread.Sleep(1000); // Wait another second before next attempt
|
await Task.Delay(1000, cancellationToken); // Wait another second before next attempt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
|
|
||||||
_logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
|
_logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus);
|
||||||
|
|
||||||
return Task.FromResult(finalStatus.ToString());
|
return finalStatus.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ namespace Managing.Application.ManageBot
|
|||||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
|
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
|
||||||
};
|
};
|
||||||
|
|
||||||
var tradingBot = _botFactory.CreateTradingBot(configToUse);
|
var tradingBot = await _botFactory.CreateTradingBot(configToUse);
|
||||||
tradingBot.User = request.User;
|
tradingBot.User = request.User;
|
||||||
|
|
||||||
// Log the configuration being used
|
// Log the configuration being used
|
||||||
await LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
|
LogBotConfigurationAsync(tradingBot, $"{configToUse.Name} created");
|
||||||
|
|
||||||
_botService.AddTradingBotToCache(tradingBot);
|
_botService.AddTradingBotToCache(tradingBot);
|
||||||
return tradingBot.GetStatus();
|
return tradingBot.GetStatus();
|
||||||
@@ -98,7 +98,7 @@ namespace Managing.Application.ManageBot
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bot">The trading bot instance</param>
|
/// <param name="bot">The trading bot instance</param>
|
||||||
/// <param name="context">Context information for the log</param>
|
/// <param name="context">Context information for the log</param>
|
||||||
private async Task LogBotConfigurationAsync(ITradingBot bot, string context)
|
private void LogBotConfigurationAsync(ITradingBot bot, string context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="MoneyManagements\Abstractions\**" />
|
<Compile Remove="MoneyManagements\Abstractions\**"/>
|
||||||
<EmbeddedResource Remove="MoneyManagements\Abstractions\**" />
|
<EmbeddedResource Remove="MoneyManagements\Abstractions\**"/>
|
||||||
<None Remove="MoneyManagements\Abstractions\**" />
|
<None Remove="MoneyManagements\Abstractions\**"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentValidation" Version="11.9.1" />
|
<PackageReference Include="FluentValidation" Version="11.9.1"/>
|
||||||
<PackageReference Include="GeneticSharp" Version="3.1.4" />
|
<PackageReference Include="GeneticSharp" Version="3.1.4"/>
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.2.0"/>
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
|
||||||
<PackageReference Include="Polly" Version="8.4.0" />
|
<PackageReference Include="Polly" Version="8.4.0"/>
|
||||||
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" />
|
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
|
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
|
||||||
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
|
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
||||||
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
|
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj"/>
|
||||||
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj" />
|
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -38,9 +38,28 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
|
|
||||||
if (moneyManagement == null)
|
if (moneyManagement == null)
|
||||||
{
|
{
|
||||||
request.User = user;
|
// Convert MoneyManagement to LightMoneyManagement for insertion
|
||||||
await _settingsRepository.InsertMoneyManagement(request);
|
var lightRequest = new LightMoneyManagement
|
||||||
return request;
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
Timeframe = request.Timeframe,
|
||||||
|
StopLoss = request.StopLoss,
|
||||||
|
TakeProfit = request.TakeProfit,
|
||||||
|
Leverage = request.Leverage
|
||||||
|
};
|
||||||
|
|
||||||
|
await _settingsRepository.InsertMoneyManagement(lightRequest, user);
|
||||||
|
|
||||||
|
// Return the created money management with user
|
||||||
|
return new MoneyManagement
|
||||||
|
{
|
||||||
|
Name = lightRequest.Name,
|
||||||
|
Timeframe = lightRequest.Timeframe,
|
||||||
|
StopLoss = lightRequest.StopLoss,
|
||||||
|
TakeProfit = lightRequest.TakeProfit,
|
||||||
|
Leverage = lightRequest.Leverage,
|
||||||
|
User = user
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -51,14 +70,28 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
"You do not have permission to update this money management strategy.");
|
"You do not have permission to update this money management strategy.");
|
||||||
}
|
}
|
||||||
|
|
||||||
moneyManagement.StopLoss = request.StopLoss;
|
// Convert to LightMoneyManagement for update
|
||||||
moneyManagement.TakeProfit = request.TakeProfit;
|
var lightRequest = new LightMoneyManagement
|
||||||
moneyManagement.Leverage = request.Leverage;
|
{
|
||||||
moneyManagement.Timeframe = request.Timeframe;
|
Name = request.Name,
|
||||||
moneyManagement.User = user;
|
Timeframe = request.Timeframe,
|
||||||
|
StopLoss = request.StopLoss,
|
||||||
|
TakeProfit = request.TakeProfit,
|
||||||
|
Leverage = request.Leverage
|
||||||
|
};
|
||||||
|
|
||||||
_settingsRepository.UpdateMoneyManagement(moneyManagement);
|
await _settingsRepository.UpdateMoneyManagementAsync(lightRequest, user);
|
||||||
return moneyManagement;
|
|
||||||
|
// Return updated money management
|
||||||
|
return new MoneyManagement
|
||||||
|
{
|
||||||
|
Name = lightRequest.Name,
|
||||||
|
Timeframe = lightRequest.Timeframe,
|
||||||
|
StopLoss = lightRequest.StopLoss,
|
||||||
|
TakeProfit = lightRequest.TakeProfit,
|
||||||
|
Leverage = lightRequest.Leverage,
|
||||||
|
User = user
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,18 +100,18 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
return await _settingsRepository.GetMoneyManagement(name);
|
return await _settingsRepository.GetMoneyManagement(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<MoneyManagement> GetMoneyMangements(User user)
|
public async Task<IEnumerable<MoneyManagement>> GetMoneyMangements(User user)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Try to use user-specific repository method first
|
// Try to use user-specific repository method first
|
||||||
return _settingsRepository.GetMoneyManagementsByUser(user);
|
return await _settingsRepository.GetMoneyManagementsByUserAsync(user);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Fall back to filtering if user-specific endpoint is not implemented
|
// Fall back to filtering if user-specific endpoint is not implemented
|
||||||
return _settingsRepository.GetMoneyManagements()
|
var allMoneyManagements = await _settingsRepository.GetMoneyManagementsAsync();
|
||||||
.Where(mm => mm.User?.Name == user.Name);
|
return allMoneyManagements.Where(mm => mm.User?.Name == user.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +139,19 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
return moneyManagement;
|
return moneyManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteMoneyManagement(User user, string name)
|
public async Task<bool> DeleteMoneyManagement(User user, string name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Try to use user-specific repository method first
|
// Try to use user-specific repository method first
|
||||||
_settingsRepository.DeleteMoneyManagementByUser(user, name);
|
await _settingsRepository.DeleteMoneyManagementByUserAsync(user, name);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Fall back to verifying user ownership before deletion
|
// Fall back to verifying user ownership before deletion
|
||||||
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result;
|
var moneyManagement = await _settingsRepository.GetMoneyManagement(name);
|
||||||
|
|
||||||
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
|
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
|
||||||
{
|
{
|
||||||
@@ -126,7 +159,7 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
"You do not have permission to delete this money management strategy.");
|
"You do not have permission to delete this money management strategy.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_settingsRepository.DeleteMoneyManagement(name);
|
await _settingsRepository.DeleteMoneyManagementAsync(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -138,14 +171,14 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteMoneyManagements(User user)
|
public async Task<bool> DeleteMoneyManagements(User user)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Try to use user-specific repository method first
|
// Try to use user-specific repository method first
|
||||||
_settingsRepository.DeleteMoneyManagementsByUser(user);
|
await _settingsRepository.DeleteMoneyManagementsByUserAsync(user);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Managing.Domain.Scenarios;
|
|||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MongoDB.Driver;
|
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Scenarios
|
namespace Managing.Application.Scenarios
|
||||||
@@ -20,20 +19,20 @@ namespace Managing.Application.Scenarios
|
|||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1)
|
public async Task<Scenario> CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1)
|
||||||
{
|
{
|
||||||
var scenario = new Scenario(name, loopbackPeriod);
|
var scenario = new Scenario(name, loopbackPeriod);
|
||||||
|
|
||||||
foreach (var strategy in strategies)
|
foreach (var strategy in strategies)
|
||||||
{
|
{
|
||||||
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy));
|
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tradingService.InsertScenario(scenario);
|
await _tradingService.InsertScenarioAsync(scenario);
|
||||||
}
|
}
|
||||||
catch (MongoCommandException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex.Message);
|
_logger.LogError(ex.Message);
|
||||||
throw new Exception("Cannot create scenario");
|
throw new Exception("Cannot create scenario");
|
||||||
@@ -42,7 +41,7 @@ namespace Managing.Application.Scenarios
|
|||||||
return scenario;
|
return scenario;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Indicator CreateStrategy(
|
public async Task<Indicator> CreateStrategy(
|
||||||
IndicatorType type,
|
IndicatorType type,
|
||||||
string name,
|
string name,
|
||||||
int? period = null,
|
int? period = null,
|
||||||
@@ -65,30 +64,25 @@ namespace Managing.Application.Scenarios
|
|||||||
stochPeriods,
|
stochPeriods,
|
||||||
smoothPeriods,
|
smoothPeriods,
|
||||||
cyclePeriods);
|
cyclePeriods);
|
||||||
_tradingService.InsertStrategy(strategy);
|
await _tradingService.InsertStrategyAsync(strategy);
|
||||||
return strategy;
|
return strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Scenario> GetScenarios()
|
public async Task<IEnumerable<Scenario>> GetScenariosAsync()
|
||||||
{
|
{
|
||||||
return _tradingService.GetScenarios();
|
return await _tradingService.GetScenariosAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scenario GetScenario(string name)
|
public async Task<IEnumerable<Indicator>> GetIndicatorsAsync()
|
||||||
{
|
{
|
||||||
return _tradingService.GetScenarioByName(name);
|
return await _tradingService.GetStrategiesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Indicator> GetIndicators()
|
public async Task<bool> DeleteScenarioAsync(string name)
|
||||||
{
|
|
||||||
return _tradingService.GetStrategies();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DeleteScenario(string name)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tradingService.DeleteScenario(name);
|
await _tradingService.DeleteScenarioAsync(name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -98,61 +92,19 @@ namespace Managing.Application.Scenarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteStrategy(string name)
|
public async Task<bool> UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tradingService.DeleteStrategy(name);
|
var scenario = await _tradingService.GetScenarioByNameAsync(name);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex.Message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DeleteStrategies()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_tradingService.DeleteStrategies();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DeleteScenarios()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_tradingService.DeleteScenarios();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UpdateScenario(string name, List<string> strategies, int? loopbackPeriod)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var scenario = _tradingService.GetScenarioByName(name);
|
|
||||||
scenario.Indicators.Clear();
|
scenario.Indicators.Clear();
|
||||||
foreach (var strategy in strategies)
|
foreach (var strategy in strategies)
|
||||||
{
|
{
|
||||||
scenario.AddIndicator(_tradingService.GetStrategyByName(strategy));
|
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
scenario.LoopbackPeriod = loopbackPeriod ?? 1;
|
scenario.LoopbackPeriod = loopbackPeriod ?? 1;
|
||||||
_tradingService.UpdateScenario(scenario);
|
await _tradingService.UpdateScenarioAsync(scenario);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -162,13 +114,13 @@ namespace Managing.Application.Scenarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
public async Task<bool> UpdateStrategy(IndicatorType indicatorType, string name, int? period, int? fastPeriods,
|
||||||
int? slowPeriods,
|
int? slowPeriods,
|
||||||
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
|
int? signalPeriods, double? multiplier, int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var strategy = _tradingService.GetStrategyByName(name);
|
var strategy = await _tradingService.GetStrategyByNameAsync(name);
|
||||||
strategy.Type = indicatorType;
|
strategy.Type = indicatorType;
|
||||||
strategy.Period = period;
|
strategy.Period = period;
|
||||||
strategy.FastPeriods = fastPeriods;
|
strategy.FastPeriods = fastPeriods;
|
||||||
@@ -178,7 +130,7 @@ namespace Managing.Application.Scenarios
|
|||||||
strategy.StochPeriods = stochPeriods;
|
strategy.StochPeriods = stochPeriods;
|
||||||
strategy.SmoothPeriods = smoothPeriods;
|
strategy.SmoothPeriods = smoothPeriods;
|
||||||
strategy.CyclePeriods = cyclePeriods;
|
strategy.CyclePeriods = cyclePeriods;
|
||||||
_tradingService.UpdateStrategy(strategy);
|
await _tradingService.UpdateStrategyAsync(strategy);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -188,15 +140,13 @@ namespace Managing.Application.Scenarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User-specific methods implementation
|
public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
|
||||||
|
|
||||||
public IEnumerable<Scenario> GetScenariosByUser(User user)
|
|
||||||
{
|
{
|
||||||
var scenarios = _tradingService.GetScenarios();
|
var scenarios = await _tradingService.GetScenariosAsync();
|
||||||
return scenarios.Where(s => s.User?.Name == user.Name);
|
return scenarios.Where(s => s.User?.Name == user.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scenario CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
|
public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, int? loopbackPeriod = 1)
|
||||||
{
|
{
|
||||||
var scenario = new Scenario(name, loopbackPeriod ?? 1)
|
var scenario = new Scenario(name, loopbackPeriod ?? 1)
|
||||||
{
|
{
|
||||||
@@ -205,82 +155,104 @@ namespace Managing.Application.Scenarios
|
|||||||
|
|
||||||
foreach (var strategyName in strategies)
|
foreach (var strategyName in strategies)
|
||||||
{
|
{
|
||||||
var strategy = _tradingService.GetStrategyByName(strategyName);
|
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
|
||||||
if (strategy != null && strategy.User?.Name == user.Name)
|
if (strategy != null && strategy.User?.Name == user.Name)
|
||||||
{
|
{
|
||||||
scenario.AddIndicator(strategy);
|
scenario.AddIndicator(strategy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_tradingService.InsertScenario(scenario);
|
await _tradingService.InsertScenarioAsync(scenario);
|
||||||
return scenario;
|
return scenario;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Indicator> GetIndicatorsByUser(User user)
|
public async Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user)
|
||||||
{
|
{
|
||||||
var strategies = _tradingService.GetStrategies();
|
var indicators = await GetIndicatorsAsync();
|
||||||
return strategies.Where(s => s.User?.Name == user.Name);
|
return indicators.Where(s => s.User?.Name == user.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteIndicatorByUser(User user, string name)
|
public async Task<bool> DeleteIndicatorByUser(User user, string name)
|
||||||
{
|
{
|
||||||
var strategy = _tradingService.GetStrategyByName(name);
|
var strategy = await _tradingService.GetStrategyByNameAsync(name);
|
||||||
if (strategy != null && strategy.User?.Name == user.Name)
|
if (strategy != null && strategy.User?.Name == user.Name)
|
||||||
{
|
{
|
||||||
_tradingService.DeleteStrategy(strategy.Name);
|
await _tradingService.DeleteStrategyAsync(strategy.Name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteScenarioByUser(User user, string name)
|
public async Task<bool> DeleteScenarioByUser(User user, string name)
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(name);
|
try
|
||||||
if (scenario != null && scenario.User?.Name == user.Name)
|
|
||||||
{
|
{
|
||||||
_tradingService.DeleteScenario(scenario.Name);
|
var scenarios = await GetScenariosByUserAsync(user);
|
||||||
|
foreach (var scenario in scenarios.Where(s => s.Name == name))
|
||||||
|
{
|
||||||
|
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
return false;
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scenario GetScenarioByUser(User user, string name)
|
public async Task<bool> DeleteScenariosByUser(User user)
|
||||||
{
|
{
|
||||||
var scenario = _tradingService.GetScenarioByName(name);
|
try
|
||||||
|
{
|
||||||
|
var scenarios = await GetScenariosByUserAsync(user);
|
||||||
|
foreach (var scenario in scenarios)
|
||||||
|
{
|
||||||
|
await _tradingService.DeleteScenarioAsync(scenario.Name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Scenario> GetScenarioByUser(User user, string name)
|
||||||
|
{
|
||||||
|
var scenario = await _tradingService.GetScenarioByNameAsync(name);
|
||||||
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
|
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Indicator CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null,
|
public async Task<Indicator> CreateIndicatorForUser(User user, IndicatorType type, string name, int? period = null,
|
||||||
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
|
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
|
||||||
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
|
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
|
||||||
int? cyclePeriods = null)
|
int? cyclePeriods = null)
|
||||||
{
|
{
|
||||||
// Create a new strategy using the existing implementation
|
// Create a new strategy using the existing implementation
|
||||||
var strategy = CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
|
var strategy = await CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
|
||||||
multiplier, stochPeriods, smoothPeriods, cyclePeriods);
|
multiplier, stochPeriods, smoothPeriods, cyclePeriods);
|
||||||
|
|
||||||
// Set the user
|
// Set the user
|
||||||
strategy.User = user;
|
strategy.User = user;
|
||||||
|
|
||||||
// Update the strategy to save the user property
|
// Update the strategy to save the user property
|
||||||
_tradingService.UpdateStrategy(strategy);
|
await _tradingService.UpdateStrategyAsync(strategy);
|
||||||
|
|
||||||
return strategy;
|
return strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteStrategiesByUser(User user)
|
public async Task<bool> DeleteStrategiesByUser(User user)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var strategies = GetIndicatorsByUser(user);
|
var strategies = await GetIndicatorsByUserAsync(user);
|
||||||
|
|
||||||
foreach (var strategy in strategies)
|
foreach (var strategy in strategies)
|
||||||
{
|
{
|
||||||
_tradingService.DeleteStrategy(strategy.Name);
|
await _tradingService.DeleteStrategyAsync(strategy.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -290,29 +262,9 @@ namespace Managing.Application.Scenarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteScenariosByUser(User user)
|
public async Task<bool> UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
|
||||||
{
|
{
|
||||||
try
|
var scenario = await _tradingService.GetScenarioByNameAsync(name);
|
||||||
{
|
|
||||||
var scenarios = GetScenariosByUser(user);
|
|
||||||
|
|
||||||
foreach (var scenario in scenarios)
|
|
||||||
{
|
|
||||||
_tradingService.DeleteScenario(scenario.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex.Message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UpdateScenarioByUser(User user, string name, List<string> strategies, int? loopbackPeriod)
|
|
||||||
{
|
|
||||||
var scenario = _tradingService.GetScenarioByName(name);
|
|
||||||
if (scenario == null || scenario.User?.Name != user.Name)
|
if (scenario == null || scenario.User?.Name != user.Name)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -323,29 +275,29 @@ namespace Managing.Application.Scenarios
|
|||||||
|
|
||||||
foreach (var strategyName in strategies)
|
foreach (var strategyName in strategies)
|
||||||
{
|
{
|
||||||
var strategy = _tradingService.GetStrategyByName(strategyName);
|
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
|
||||||
if (strategy != null && strategy.User?.Name == user.Name)
|
if (strategy != null && strategy.User?.Name == user.Name)
|
||||||
{
|
{
|
||||||
scenario.AddIndicator(strategy);
|
scenario.AddIndicator(strategy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_tradingService.UpdateScenario(scenario);
|
await _tradingService.UpdateScenarioAsync(scenario);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
|
public async Task<bool> UpdateIndicatorByUser(User user, IndicatorType indicatorType, string name, int? period,
|
||||||
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
|
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
|
||||||
int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
|
int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
|
||||||
{
|
{
|
||||||
var strategy = _tradingService.GetStrategyByName(name);
|
var strategy = await _tradingService.GetStrategyByNameAsync(name);
|
||||||
if (strategy == null || strategy.User?.Name != user.Name)
|
if (strategy == null || strategy.User?.Name != user.Name)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the existing update strategy logic
|
// Use the existing update strategy logic
|
||||||
var result = UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods,
|
var result = await UpdateStrategy(indicatorType, name, period, fastPeriods, slowPeriods,
|
||||||
signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods);
|
signalPeriods, multiplier, stochPeriods, smoothPeriods, cyclePeriods);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ public class MessengerService : IMessengerService
|
|||||||
// If user is provided, also send to webhook
|
// If user is provided, also send to webhook
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
user = _userService.GetUser(user.Name);
|
|
||||||
await _webhookService.SendTradeNotification(user, message, isBadBehavior);
|
await _webhookService.SendTradeNotification(user, message, isBadBehavior);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,18 +32,7 @@ public class SettingsService : ISettingsService
|
|||||||
throw new Exception("Cannot delete all backtests");
|
throw new Exception("Cannot delete all backtests");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_scenarioService.DeleteScenarios())
|
if (!await SetupSettings())
|
||||||
{
|
|
||||||
throw new Exception("Cannot delete scenarios");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_scenarioService.DeleteStrategies())
|
|
||||||
{
|
|
||||||
throw new Exception("Cannot delete all strategies");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!SetupSettings())
|
|
||||||
{
|
{
|
||||||
throw new Exception("Cannot setup settings");
|
throw new Exception("Cannot setup settings");
|
||||||
}
|
}
|
||||||
@@ -51,7 +40,7 @@ public class SettingsService : ISettingsService
|
|||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetupSettings()
|
public async Task<bool> SetupSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -59,7 +48,7 @@ public class SettingsService : ISettingsService
|
|||||||
// SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
|
// SetupMoneyManagementsSeed(Timeframe.FifteenMinutes);
|
||||||
// SetupMoneyManagementsSeed(Timeframe.OneHour);
|
// SetupMoneyManagementsSeed(Timeframe.OneHour);
|
||||||
// SetupMoneyManagementsSeed(Timeframe.OneDay);
|
// SetupMoneyManagementsSeed(Timeframe.OneDay);
|
||||||
SetupScenariosSeed();
|
await SetupScenariosSeed();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -85,107 +74,107 @@ public class SettingsService : ISettingsService
|
|||||||
// await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
|
// await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private void SetupScenariosSeed()
|
private async Task SetupScenariosSeed()
|
||||||
{
|
{
|
||||||
SetupMacd();
|
await SetupMacd();
|
||||||
SetupRsiDiv();
|
await SetupRsiDiv();
|
||||||
SetupRsiDivConfirm();
|
await SetupRsiDivConfirm();
|
||||||
SetupSuperTrend();
|
await SetupSuperTrend();
|
||||||
SetupChandelierExit();
|
await SetupChandelierExit();
|
||||||
SetupStochRsiTrend();
|
await SetupStochRsiTrend();
|
||||||
SetupStochSTCTrend();
|
await SetupStochSTCTrend();
|
||||||
SetupEmaTrend();
|
await SetupEmaTrend();
|
||||||
SetupEmaCross();
|
await SetupEmaCross();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupStochSTCTrend()
|
private async Task SetupStochSTCTrend()
|
||||||
{
|
{
|
||||||
var name = "STCTrend";
|
var name = "STCTrend";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.Stc,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.Stc,
|
||||||
name,
|
name,
|
||||||
fastPeriods: 23,
|
fastPeriods: 23,
|
||||||
slowPeriods: 50,
|
slowPeriods: 50,
|
||||||
cyclePeriods: 10);
|
cyclePeriods: 10);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupMacd()
|
private async Task SetupMacd()
|
||||||
{
|
{
|
||||||
var name = "MacdCross";
|
var name = "MacdCross";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.MacdCross,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.MacdCross,
|
||||||
name,
|
name,
|
||||||
fastPeriods: 12,
|
fastPeriods: 12,
|
||||||
slowPeriods: 26,
|
slowPeriods: 26,
|
||||||
signalPeriods: 9);
|
signalPeriods: 9);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupRsiDiv()
|
private async Task SetupRsiDiv()
|
||||||
{
|
{
|
||||||
var name = "RsiDiv6";
|
var name = "RsiDiv6";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergence,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergence,
|
||||||
name,
|
name,
|
||||||
period: 6);
|
period: 6);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupRsiDivConfirm()
|
private async Task SetupRsiDivConfirm()
|
||||||
{
|
{
|
||||||
var name = "RsiDivConfirm6";
|
var name = "RsiDivConfirm6";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.RsiDivergenceConfirm,
|
||||||
name,
|
name,
|
||||||
period: 6);
|
period: 6);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupSuperTrend()
|
private async Task SetupSuperTrend()
|
||||||
{
|
{
|
||||||
var name = "SuperTrend";
|
var name = "SuperTrend";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.SuperTrend,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.SuperTrend,
|
||||||
name,
|
name,
|
||||||
period: 10,
|
period: 10,
|
||||||
multiplier: 3);
|
multiplier: 3);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupChandelierExit()
|
private async Task SetupChandelierExit()
|
||||||
{
|
{
|
||||||
var name = "ChandelierExit";
|
var name = "ChandelierExit";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.ChandelierExit,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.ChandelierExit,
|
||||||
name,
|
name,
|
||||||
period: 22,
|
period: 22,
|
||||||
multiplier: 3);
|
multiplier: 3);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupStochRsiTrend()
|
private async Task SetupStochRsiTrend()
|
||||||
{
|
{
|
||||||
var name = "StochRsiTrend";
|
var name = "StochRsiTrend";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.StochRsiTrend,
|
||||||
name,
|
name,
|
||||||
period: 14,
|
period: 14,
|
||||||
stochPeriods: 14,
|
stochPeriods: 14,
|
||||||
signalPeriods: 3,
|
signalPeriods: 3,
|
||||||
smoothPeriods: 1);
|
smoothPeriods: 1);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupEmaTrend()
|
private async Task SetupEmaTrend()
|
||||||
{
|
{
|
||||||
var name = "Ema200Trend";
|
var name = "Ema200Trend";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaTrend,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaTrend,
|
||||||
name,
|
name,
|
||||||
period: 200);
|
period: 200);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupEmaCross()
|
private async Task SetupEmaCross()
|
||||||
{
|
{
|
||||||
var name = "Ema200Cross";
|
var name = "Ema200Cross";
|
||||||
var strategy = _scenarioService.CreateStrategy(IndicatorType.EmaCross,
|
var strategy = await _scenarioService.CreateStrategy(IndicatorType.EmaCross,
|
||||||
name,
|
name,
|
||||||
period: 200);
|
period: 200);
|
||||||
_scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
await _scenarioService.CreateScenario(name, new List<string> { strategy.Name });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CreateDefaultConfiguration(User user)
|
public async Task<bool> CreateDefaultConfiguration(User user)
|
||||||
@@ -212,7 +201,7 @@ public class SettingsService : ISettingsService
|
|||||||
await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement);
|
await _moneyManagementService.CreateOrUpdateMoneyManagement(user, defaultMoneyManagement);
|
||||||
|
|
||||||
// Create default Strategy (StcTrend)
|
// Create default Strategy (StcTrend)
|
||||||
var defaultStrategy = _scenarioService.CreateIndicatorForUser(
|
var defaultStrategy = await _scenarioService.CreateIndicatorForUser(
|
||||||
user,
|
user,
|
||||||
IndicatorType.Stc,
|
IndicatorType.Stc,
|
||||||
"Stc",
|
"Stc",
|
||||||
@@ -226,7 +215,7 @@ public class SettingsService : ISettingsService
|
|||||||
|
|
||||||
// Create default Scenario containing the strategy
|
// Create default Scenario containing the strategy
|
||||||
var strategyNames = new List<string> { defaultStrategy.Name };
|
var strategyNames = new List<string> { defaultStrategy.Name };
|
||||||
var defaultScenario = _scenarioService.CreateScenarioForUser(
|
var defaultScenario = await _scenarioService.CreateScenarioForUser(
|
||||||
user,
|
user,
|
||||||
"STC Scenario",
|
"STC Scenario",
|
||||||
strategyNames
|
strategyNames
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
|
||||||
using Managing.Domain.Risk;
|
using Managing.Domain.Risk;
|
||||||
using Managing.Domain.Strategies;
|
|
||||||
using Managing.Domain.Synth.Models;
|
using Managing.Domain.Synth.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -223,7 +221,7 @@ public class SynthPredictionService : ISynthPredictionService
|
|||||||
/// <param name="isBacktest">Whether this is a backtest scenario</param>
|
/// <param name="isBacktest">Whether this is a backtest scenario</param>
|
||||||
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
|
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
|
||||||
/// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns>
|
/// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns>
|
||||||
public async Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice,
|
public async Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
|
||||||
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null)
|
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null)
|
||||||
{
|
{
|
||||||
var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);
|
var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);
|
||||||
@@ -925,7 +923,7 @@ public class SynthPredictionService : ISynthPredictionService
|
|||||||
/// Estimates liquidation price based on money management settings
|
/// Estimates liquidation price based on money management settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction,
|
public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction,
|
||||||
MoneyManagement moneyManagement)
|
LightMoneyManagement moneyManagement)
|
||||||
{
|
{
|
||||||
// This is a simplified estimation - in reality, you'd use the actual money management logic
|
// This is a simplified estimation - in reality, you'd use the actual money management logic
|
||||||
var riskPercentage = 0.02m; // Default 2% risk
|
var riskPercentage = 0.02m; // Default 2% risk
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class ClosePositionCommandHandler(
|
|||||||
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
|
||||||
if (request.Position == null)
|
if (request.Position == null)
|
||||||
{
|
{
|
||||||
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
|
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
|
||||||
return request.Position;
|
return request.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ public class ClosePositionCommandHandler(
|
|||||||
|
|
||||||
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
|
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
|
||||||
? request.ExecutionPrice.GetValueOrDefault()
|
? request.ExecutionPrice.GetValueOrDefault()
|
||||||
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
|
: await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
// Check if position still open
|
// Check if position still open
|
||||||
if (!request.IsForBacktest)
|
if (!request.IsForBacktest)
|
||||||
@@ -46,7 +46,7 @@ public class ClosePositionCommandHandler(
|
|||||||
request.Position.ProfitAndLoss =
|
request.Position.ProfitAndLoss =
|
||||||
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
|
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
|
||||||
request.Position.Open.Leverage);
|
request.Position.Open.Leverage);
|
||||||
tradingService.UpdatePosition(request.Position);
|
await tradingService.UpdatePositionAsync(request.Position);
|
||||||
return request.Position;
|
return request.Position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ public class ClosePositionCommandHandler(
|
|||||||
request.Position.Open.Leverage);
|
request.Position.Open.Leverage);
|
||||||
|
|
||||||
if (!request.IsForBacktest)
|
if (!request.IsForBacktest)
|
||||||
tradingService.UpdatePosition(request.Position);
|
await tradingService.UpdatePositionAsync(request.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.Position;
|
return request.Position;
|
||||||
|
|||||||
@@ -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 Managing.Domain.Users;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -10,7 +9,7 @@ namespace Managing.Application.Trading.Commands
|
|||||||
{
|
{
|
||||||
public OpenPositionRequest(
|
public OpenPositionRequest(
|
||||||
string accountName,
|
string accountName,
|
||||||
MoneyManagement moneyManagement,
|
LightMoneyManagement moneyManagement,
|
||||||
TradeDirection direction,
|
TradeDirection direction,
|
||||||
Ticker ticker,
|
Ticker ticker,
|
||||||
PositionInitiator initiator,
|
PositionInitiator initiator,
|
||||||
@@ -43,7 +42,7 @@ namespace Managing.Application.Trading.Commands
|
|||||||
|
|
||||||
public string SignalIdentifier { get; set; }
|
public string SignalIdentifier { get; set; }
|
||||||
public string AccountName { get; }
|
public string AccountName { get; }
|
||||||
public MoneyManagement MoneyManagement { get; }
|
public LightMoneyManagement MoneyManagement { get; }
|
||||||
public TradeDirection Direction { get; }
|
public TradeDirection Direction { get; }
|
||||||
public Ticker Ticker { get; }
|
public Ticker Ticker { get; }
|
||||||
public bool IsForPaperTrading { get; }
|
public bool IsForPaperTrading { get; }
|
||||||
|
|||||||
@@ -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;
|
_accountService = accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken)
|
public async Task<Trade> Handle(GetTradeCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var account = _accountService.GetAccount(request.AccountName, true, false).Result;
|
var account = await _accountService.GetAccount(request.AccountName, true, false);
|
||||||
return _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
|
return await _exchangeService.GetTrade(account, request.ExchangeOrderId, request.Ticker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user